incyclist-services 1.6.5 → 1.6.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,6 +3,7 @@ import { Observer } from "../../base/types";
3
3
  import { CoachesService } from "../../coaches";
4
4
  import { OnlineStateMonitoringService } from "../../monitoring";
5
5
  import { RouteListService } from "../../routes";
6
+ import { ActiveRideCount } from "../../routes/list/types";
6
7
  import { UserSettingsService } from "../../settings";
7
8
  import { ActivityRouteType } from "../base";
8
9
  import { IncyclistActiveRidesApi } from "../base/api/active-rides";
@@ -25,6 +26,8 @@ export declare class ActiveRidesService extends IncyclistService {
25
26
  protected mq: ActiveRideListMessageQueue;
26
27
  protected prevLogTS: number;
27
28
  protected isStarted: boolean;
29
+ protected routeListObserver: Observer;
30
+ protected routesStats: ActiveRideCount[];
28
31
  protected readonly activityEventHandlers: {
29
32
  update: any;
30
33
  start: any;
@@ -36,6 +39,9 @@ export declare class ActiveRidesService extends IncyclistService {
36
39
  get(): ActiveRideEntry[];
37
40
  getObserver(): Observer;
38
41
  stop(): void;
42
+ protected countRidesByRoute(): Map<string, number>;
43
+ protected onRoutePageOpened(observer: Observer): Promise<void>;
44
+ protected getActiveRideCounts(): Promise<ActiveRideCount[]>;
39
45
  protected start(session: string): void;
40
46
  protected initList(): Promise<void>;
41
47
  protected onActivityUpdated(): void;
@@ -70,8 +76,10 @@ export declare class ActiveRidesService extends IncyclistService {
70
76
  initial: boolean;
71
77
  }): Promise<void>;
72
78
  protected subscribeActivityEvents(): Promise<void>;
79
+ protected onInfoEvent(topic: string): void;
73
80
  protected onActivityEvent(topic: string, payload: ActiveRideListMessage): void;
74
81
  protected publishStartEvent(): void;
82
+ protected onActivityInfoEvent(session: string): void;
75
83
  protected onActivityStartEvent(session: string, payload: ActivityStartMessage): void;
76
84
  protected getRemoteActivityDetails(sessionId: string): Promise<ActiveRideEntry>;
77
85
  protected publishUpdateEvent(payload: any): void;
@@ -80,11 +80,17 @@ let ActiveRidesService = (() => {
80
80
  this.apiState = { busy: false };
81
81
  this.prevLogTS = 0;
82
82
  this.isStarted = false;
83
+ this.routesStats = [];
83
84
  this.activityEventHandlers = {
84
85
  update: this.onActivityUpdateEvent.bind(this),
85
86
  start: this.onActivityStartEvent.bind(this),
86
87
  stop: this.onActivityStopEvent.bind(this)
87
88
  };
89
+ const routeList = this.getRouteList();
90
+ routeList.on('opened', this.onRoutePageOpened.bind(this));
91
+ routeList.on('closed', () => {
92
+ delete this.routeListObserver;
93
+ });
88
94
  }
89
95
  init(session, maxLength = 10) {
90
96
  this.maxLength = maxLength;
@@ -141,6 +147,56 @@ let ActiveRidesService = (() => {
141
147
  this.logError(err, 'stop');
142
148
  }
143
149
  }
150
+ countRidesByRoute() {
151
+ const map = new Map();
152
+ const rides = this.get();
153
+ rides.forEach(ride => {
154
+ const routeHash = ride.ride.routeHash;
155
+ if (!map.has(routeHash)) {
156
+ map.set(routeHash, 0);
157
+ }
158
+ const count = map.get(routeHash);
159
+ map.set(routeHash, count + 1);
160
+ });
161
+ return map;
162
+ }
163
+ onRoutePageOpened(observer) {
164
+ return __awaiter(this, void 0, void 0, function* () {
165
+ this.routeListObserver = observer;
166
+ yield this.getActiveRideCounts();
167
+ this.routeListObserver.emit('stats-update', this.routesStats);
168
+ });
169
+ }
170
+ getActiveRideCounts() {
171
+ return __awaiter(this, void 0, void 0, function* () {
172
+ try {
173
+ const isOnline = this.getOnlineStatusMonitoring().onlineStatus;
174
+ if (!isOnline)
175
+ return;
176
+ const activeRides = yield this.getApi().getAll();
177
+ const routes = this.getRouteList().getAllRoutes();
178
+ const counts = [];
179
+ routes.forEach(route => {
180
+ var _a;
181
+ const routeRides = (_a = activeRides.filter(ar => ar.ride.routeHash === route.description.routeHash)) !== null && _a !== void 0 ? _a : [];
182
+ const count = routeRides.length;
183
+ counts.push({
184
+ count,
185
+ routeId: route.description.id,
186
+ routeHash: route.description.routeHash
187
+ });
188
+ });
189
+ this.routesStats = counts;
190
+ return counts;
191
+ }
192
+ catch (err) {
193
+ const isOnline = this.getOnlineStatusMonitoring().onlineStatus;
194
+ if (isOnline)
195
+ this.logError(err, 'getActiveRideCounts');
196
+ }
197
+ return [];
198
+ });
199
+ }
144
200
  start(session) {
145
201
  if (this.onlineStatusHandler || this.isOnline) {
146
202
  this.initList();
@@ -501,13 +557,21 @@ let ActiveRidesService = (() => {
501
557
  }
502
558
  subscribeActivityEvents() {
503
559
  return __awaiter(this, void 0, void 0, function* () {
504
- var _a;
560
+ var _a, _b;
505
561
  const hash = (_a = this.current) === null || _a === void 0 ? void 0 : _a.ride.routeHash;
506
- const topic = `incyclist/activity/+/${hash}/+`;
507
- this.getMessageQueue().subscribe(topic, this.onActivityEvent.bind(this), 'incyclist/activity');
562
+ const session = (_b = this.current) === null || _b === void 0 ? void 0 : _b.sessionId;
563
+ const updateTopic = `incyclist/activity/+/${hash}/+`;
564
+ this.getMessageQueue().subscribe(updateTopic, this.onActivityEvent.bind(this), 'incyclist/activity');
565
+ const infoTopic = `incyclist/session/${session}/info`;
566
+ this.getMessageQueue().subscribe(infoTopic, this.onInfoEvent.bind(this), 'incyclist/session');
508
567
  this.isSubscribed = true;
509
568
  });
510
569
  }
570
+ onInfoEvent(topic) {
571
+ const keys = topic.split('/');
572
+ const session = keys[2];
573
+ this.onActivityInfoEvent(session);
574
+ }
511
575
  onActivityEvent(topic, payload) {
512
576
  const keys = topic.split('/');
513
577
  const session = keys[2];
@@ -529,6 +593,21 @@ let ActiveRidesService = (() => {
529
593
  const topic = `incyclist/activity/${this.session}/${this.getRouteHash()}/start`;
530
594
  this.getMessageQueue().sendMessage(topic, payload);
531
595
  }
596
+ onActivityInfoEvent(session) {
597
+ this.logEvent({ message: 'Received session info request', sessionId: session });
598
+ if (session !== this.current.sessionId)
599
+ return;
600
+ const payload = {
601
+ user: this.current.user,
602
+ ride: this.current.ride,
603
+ position: this.current.currentPosition,
604
+ rideDistance: this.current.currentRideDistance,
605
+ duration: this.current.currentDuration,
606
+ lap: this.current.currentLap
607
+ };
608
+ const topic = `incyclist/activity/${this.session}/${this.getRouteHash()}/info`;
609
+ this.getMessageQueue().sendMessage(topic, payload);
610
+ }
532
611
  onActivityStartEvent(session, payload) {
533
612
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
534
613
  const prevActive = (_a = this.others) === null || _a === void 0 ? void 0 : _a.length;
@@ -63,6 +63,11 @@ export interface ActivityUpdateMessage extends ActiveRideListMessage {
63
63
  duration?: number;
64
64
  isPaused?: boolean;
65
65
  }
66
+ export interface ActivityInfoMessage extends ActivityStartMessage {
67
+ rideDistance: number;
68
+ lap?: number;
69
+ duration?: number;
70
+ }
66
71
  export type ActiveRideListAvatar = {
67
72
  shirt: string;
68
73
  helmet: string;
@@ -151,6 +151,7 @@ let FreeRideDisplayService = (() => {
151
151
  onTurn() {
152
152
  return __awaiter(this, void 0, void 0, function* () {
153
153
  var _b, _c, _d, _e;
154
+ this.logEvent({ message: 'initiating turn', position: this.position });
154
155
  this.turnPosition = this.position;
155
156
  this.isTurnEnabled = false;
156
157
  this.tsLastTurn = Date.now();
@@ -164,16 +164,22 @@ let RouteDisplayService = (() => {
164
164
  }
165
165
  getNearbyRidesProps(props) {
166
166
  var _b, _c;
167
- const { minimized } = this.getOverlayProps('', props);
168
- const hasNearbyRides = ((_b = this.getActiveRides().get()) === null || _b === void 0 ? void 0 : _b.length) > 0;
169
- const show = hasNearbyRides && !props.hideAll;
170
- const observer = (_c = this.getActiveRides().getObserver()) !== null && _c !== void 0 ? _c : null;
171
- const nearbyRides = { show, minimized, observer };
172
- if (this.hasNearbyRides !== hasNearbyRides) {
173
- this.hasNearbyRides = hasNearbyRides;
174
- this.service.getObserver().emit('overlay-update', { nearbyRides });
167
+ try {
168
+ const { minimized } = this.getOverlayProps('', props);
169
+ const hasNearbyRides = ((_b = this.getActiveRides().get()) === null || _b === void 0 ? void 0 : _b.length) > 0;
170
+ const show = hasNearbyRides && !props.hideAll;
171
+ const observer = (_c = this.getActiveRides().getObserver()) !== null && _c !== void 0 ? _c : null;
172
+ const nearbyRides = { show, minimized, observer };
173
+ if (this.hasNearbyRides !== hasNearbyRides) {
174
+ this.hasNearbyRides = hasNearbyRides;
175
+ this.service.getObserver().emit('overlay-update', { nearbyRides });
176
+ }
177
+ return nearbyRides;
178
+ }
179
+ catch (err) {
180
+ this.logError(err, 'getNearbyRidesProps');
181
+ return { show: false, minimized: true, observer: null };
175
182
  }
176
- return nearbyRides;
177
183
  }
178
184
  getDisplayProperties(props) {
179
185
  const { realityFactor, startPos, endPos } = this.startSettings;
@@ -11,6 +11,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.GPXParser = void 0;
13
13
  const utils_1 = require("../../../utils");
14
+ const math_1 = require("../../../utils/math");
14
15
  const valid_1 = require("../../../utils/valid");
15
16
  const route_1 = require("../utils/route");
16
17
  const xml_1 = require("./xml");
@@ -120,12 +121,20 @@ class GPXParser extends xml_1.XMLParser {
120
121
  }
121
122
  points.forEach((gpxPt) => {
122
123
  const point = {
123
- lat: Number(gpxPt.lat),
124
- lng: Number(gpxPt.lon),
125
- elevation: gpxPt.ele ? Number(gpxPt.ele) : prev === null || prev === void 0 ? void 0 : prev.elevation,
124
+ lat: (0, math_1.num)(gpxPt.lat),
125
+ lng: (0, math_1.num)(gpxPt.lon),
126
+ elevation: gpxPt.ele ? (0, math_1.num)(gpxPt.ele) : prev === null || prev === void 0 ? void 0 : prev.elevation,
126
127
  routeDistance: 0,
127
128
  distance: 0
128
129
  };
130
+ if (point.lat === undefined || point.lng === undefined) {
131
+ const info = {
132
+ original: gpxPt,
133
+ parsed: point
134
+ };
135
+ this.logger.logEvent({ message: 'error', fn: 'loadPoints', reason: 'unvalid point', info });
136
+ return;
137
+ }
129
138
  if (this.props.addTime) {
130
139
  if (startTime === null) {
131
140
  point.time = 0;
@@ -94,26 +94,35 @@ let FreeRideService = (() => {
94
94
  }
95
95
  getNextOptions(forStart) {
96
96
  return __awaiter(this, void 0, void 0, function* () {
97
- var _a, _b;
97
+ var _a, _b, _c;
98
98
  let prepared = false;
99
99
  const from = this.selectedOption;
100
100
  const optionsInfo = (opts) => {
101
101
  var _a;
102
102
  return (_a = opts === null || opts === void 0 ? void 0 : opts.map(o => this.buildId(o)).join('|')) !== null && _a !== void 0 ? _a : 'none';
103
103
  };
104
+ let loadAsync = false;
104
105
  if (((_a = from === null || from === void 0 ? void 0 : from.options) === null || _a === void 0 ? void 0 : _a.length) > 0) {
105
106
  this.options = this.evaluateOptions(from.options, from);
106
- prepared = true;
107
- this.getNextLevelOptions(from.options);
107
+ if ((_b = this.options) === null || _b === void 0 ? void 0 : _b.length) {
108
+ prepared = true;
109
+ this.getNextLevelOptions(from.options);
110
+ }
111
+ else {
112
+ loadAsync = true;
113
+ }
108
114
  }
109
- else {
115
+ if (loadAsync) {
110
116
  this.options = yield this.loadNextOptions(from, forStart);
111
117
  }
112
118
  const message = forStart ? 'start options updated' : 'free ride options updated';
113
119
  this.logEvent({ message, prepared, options: optionsInfo(this.options), from: this.buildId(from) });
114
- if (((_b = this.options) === null || _b === void 0 ? void 0 : _b.length) > 0) {
120
+ if (((_c = this.options) === null || _c === void 0 ? void 0 : _c.length) > 0) {
115
121
  this.selectOption(this.options[0]);
116
122
  }
123
+ else {
124
+ this.selectedOption = undefined;
125
+ }
117
126
  return this.options;
118
127
  });
119
128
  }
@@ -362,16 +371,17 @@ let FreeRideService = (() => {
362
371
  selectOption(option) {
363
372
  var _a;
364
373
  const current = this.selectedOption;
374
+ const options = (_a = this.options) !== null && _a !== void 0 ? _a : [];
365
375
  let opt;
366
376
  switch (typeof option) {
367
377
  case 'number':
368
- opt = (_a = this.options) === null || _a === void 0 ? void 0 : _a[option - 1];
378
+ opt = options[option - 1];
369
379
  break;
370
380
  case 'string':
371
- opt = this.options.find(o => this.buildId(o) === option);
381
+ opt = options.find(o => this.buildId(o) === option);
372
382
  if (!opt) {
373
383
  const wayId = option.split(':')[0];
374
- opt = this.options.find(o => o.id === wayId);
384
+ opt = options.find(o => o.id === wayId);
375
385
  }
376
386
  if (!opt) {
377
387
  opt = current;
@@ -380,9 +390,9 @@ let FreeRideService = (() => {
380
390
  default:
381
391
  opt = option;
382
392
  }
383
- this.selectedOption = this.options.find(o => this.buildId(o) === this.buildId(opt));
393
+ this.selectedOption = options === null || options === void 0 ? void 0 : options.find(o => this.buildId(o) === this.buildId(opt));
384
394
  if (!this.selectedOption) {
385
- this.logEvent({ message: 'free ride option selection failed', requested: option, type: typeof option, options: this.options, optionKeys: this.options.map(o => this.buildId(o)) });
395
+ this.logEvent({ message: 'free ride option selection failed', requested: option, type: typeof option, options: options !== null && options !== void 0 ? options : 'none', optionKeys: options.map(o => this.buildId(o)) });
386
396
  }
387
397
  return this.options;
388
398
  }
@@ -20,6 +20,7 @@ export interface SummaryCardDisplayProps extends RouteInfo {
20
20
  initialized: boolean;
21
21
  loading?: boolean;
22
22
  isNew?: boolean;
23
+ cntActive?: number;
23
24
  }
24
25
  export interface DetailCardDisplayProps {
25
26
  }
@@ -64,6 +65,7 @@ export declare class RouteCard extends BaseCard implements Card<Route> {
64
65
  protected deleteObserver: PromiseObserver<boolean>;
65
66
  protected ready: boolean;
66
67
  protected logger: EventLogger;
68
+ protected cntActive: number;
67
69
  constructor(route: Route, props?: {
68
70
  list?: CardList<Route>;
69
71
  });
@@ -86,6 +88,7 @@ export declare class RouteCard extends BaseCard implements Card<Route> {
86
88
  setRouteData(details: RouteApiDetail): void;
87
89
  getCardType(): RouteCardType;
88
90
  getTitle(): string;
91
+ setActiveCount(cnt: number, update?: boolean): Promise<void>;
89
92
  getDisplayProperties(): SummaryCardDisplayProps;
90
93
  getMarkers(settings?: RouteSettings): Array<RoutePoint>;
91
94
  getId(): string;
@@ -195,6 +195,17 @@ class RouteCard extends base_1.BaseCard {
195
195
  getTitle() {
196
196
  return this.route.title;
197
197
  }
198
+ setActiveCount(cnt_1) {
199
+ return __awaiter(this, arguments, void 0, function* (cnt, update = true) {
200
+ var _a;
201
+ const prev = (_a = this.cntActive) !== null && _a !== void 0 ? _a : 0;
202
+ this.cntActive = cnt !== null && cnt !== void 0 ? cnt : 0;
203
+ if (update && prev !== this.cntActive) {
204
+ yield (0, utils_1.waitNextTick)();
205
+ this.emitUpdate();
206
+ }
207
+ });
208
+ }
198
209
  getDisplayProperties() {
199
210
  var _a;
200
211
  try {
@@ -211,7 +222,7 @@ class RouteCard extends base_1.BaseCard {
211
222
  let isNew = (0, utils_2.checkIsNew)(descr);
212
223
  const loaded = details !== undefined;
213
224
  const loading = this.deleteObserver !== undefined;
214
- return Object.assign(Object.assign({}, descr), { initialized: this.initialized, loaded, ready: true, state: 'loaded', visible: this.visible, isNew, canDelete: this.canDelete(), points, loading, title: this.getTitle(), observer: this.cardObserver });
225
+ return Object.assign(Object.assign({}, descr), { initialized: this.initialized, loaded, ready: true, state: 'loaded', visible: this.visible, isNew, canDelete: this.canDelete(), points, loading, title: this.getTitle(), observer: this.cardObserver, cntActive: this.cntActive });
215
226
  }
216
227
  catch (err) {
217
228
  this.logError(err, 'getDisplayProperties');
@@ -9,7 +9,7 @@ import { RouteInfo } from "../base/types";
9
9
  import { RoutesApiLoader } from "./loaders/api";
10
10
  import { MyRoutes } from "./lists/myroutes";
11
11
  import { RouteCard, SummaryCardDisplayProps } from "./cards/RouteCard";
12
- import { DisplayType, IRouteList, RouteStartSettings, SearchFilter, SearchFilterOptions } from "./types";
12
+ import { ActiveRideCount, DisplayType, IRouteList, RouteStartSettings, SearchFilter, SearchFilterOptions } from "./types";
13
13
  import { RoutesDbLoader } from "./loaders/db";
14
14
  import { RouteListObserver } from "./RouteListObserver";
15
15
  import { ActiveImportCard } from "./cards/ActiveImportCard";
@@ -43,6 +43,7 @@ export declare class RouteListService extends IncyclistService implements IRoute
43
43
  observer?: Observer;
44
44
  };
45
45
  protected currentView: 'list' | 'grid' | 'routes';
46
+ protected stats: any;
46
47
  constructor();
47
48
  setLanguage(language: string): void;
48
49
  getLanguage(): string;
@@ -93,6 +94,7 @@ export declare class RouteListService extends IncyclistService implements IRoute
93
94
  };
94
95
  getVisibleRoutes(): Array<Route>;
95
96
  getAllAppRoutes(source: string): Array<RouteInfo>;
97
+ getAllRoutes(): Array<Route>;
96
98
  searchRepo(requestedFilters?: SearchFilter): {
97
99
  routes: SummaryCardDisplayProps[];
98
100
  filters: SearchFilter;
@@ -186,6 +188,7 @@ export declare class RouteListService extends IncyclistService implements IRoute
186
188
  protected resetCards(): void;
187
189
  protected getAllSearchCards(): RouteCard[];
188
190
  protected handleConfigChanges(): void;
191
+ protected onRouteStatsUpdate(stats: ActiveRideCount[]): void;
189
192
  protected getUserSettings(): import("../../settings").UserSettingsService;
190
193
  protected getRouteSyncFactory(): RouteSyncFactory;
191
194
  protected getAppState(): import("../../appstate").AppStateService;
@@ -150,6 +150,8 @@ let RouteListService = (() => {
150
150
  }
151
151
  if (this.initialized && !hasLists)
152
152
  emitLoadedEvent();
153
+ this.observer.on('stats-update', this.onRouteStatsUpdate.bind(this));
154
+ this.emit('opened', this.observer, this.stats === undefined);
153
155
  }
154
156
  catch (err) {
155
157
  this.logError(err, 'open');
@@ -174,6 +176,7 @@ let RouteListService = (() => {
174
176
  var _a, _b;
175
177
  try {
176
178
  this.logEvent({ message: 'close route list' });
179
+ this.emit('closed', this.observer);
177
180
  (_a = this.observer) === null || _a === void 0 ? void 0 : _a.emit('stopped');
178
181
  (_b = this.observer) === null || _b === void 0 ? void 0 : _b.reset();
179
182
  this.resetCards();
@@ -210,6 +213,8 @@ let RouteListService = (() => {
210
213
  this.saveFilters(filters);
211
214
  }
212
215
  const res = this.searchRepo(filters);
216
+ this.observer.on('stats-update', this.onRouteStatsUpdate.bind(this));
217
+ this.emit('opened', this.observer, this.stats === undefined);
213
218
  this.getAppState().setPersistedState('page', 'search');
214
219
  return res;
215
220
  }
@@ -221,9 +226,13 @@ let RouteListService = (() => {
221
226
  routes = this.applySourceFilter({ routeSource: source }, routes);
222
227
  return routes;
223
228
  }
229
+ getAllRoutes() {
230
+ return this.routes;
231
+ }
224
232
  searchRepo(requestedFilters) {
225
- if (!this.observer)
233
+ if (!this.observer) {
226
234
  this.observer = new RouteListObserver_1.RouteListObserver(this);
235
+ }
227
236
  try {
228
237
  const filters = requestedFilters !== null && requestedFilters !== void 0 ? requestedFilters : this.filters;
229
238
  let routes = Array.from(this.getAllSearchCards().map(c => c.getDisplayProperties()));
@@ -1163,6 +1172,16 @@ let RouteListService = (() => {
1163
1172
  this.emitLists('updated');
1164
1173
  });
1165
1174
  }
1175
+ onRouteStatsUpdate(stats) {
1176
+ this.stats = stats;
1177
+ for (const stat of stats) {
1178
+ const id = stat.routeId;
1179
+ const card = this.getCard(id);
1180
+ if (card) {
1181
+ card.setActiveCount(stat.count);
1182
+ }
1183
+ }
1184
+ }
1166
1185
  getUserSettings() {
1167
1186
  return (0, settings_1.useUserSettings)();
1168
1187
  }
@@ -69,3 +69,8 @@ export interface IRouteList {
69
69
  };
70
70
  getAllAppRoutes(source: string): Array<RouteInfo>;
71
71
  }
72
+ export type ActiveRideCount = {
73
+ count: number;
74
+ routeId: string;
75
+ routeHash: string;
76
+ };
@@ -4,3 +4,4 @@ export declare function sin(degree: any): number;
4
4
  export declare function asin(num: any): number;
5
5
  export declare function cos(degree: any): number;
6
6
  export declare function abs(num: any): number;
7
+ export declare const num: (value: string | number) => number;
package/lib/utils/math.js CHANGED
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.num = void 0;
3
4
  exports.rad = rad;
4
5
  exports.degrees = degrees;
5
6
  exports.sin = sin;
@@ -25,3 +26,11 @@ function cos(degree) {
25
26
  function abs(num) {
26
27
  return Math.abs(num);
27
28
  }
29
+ const num = (value) => {
30
+ const res = Number(value);
31
+ if (isNaN(res)) {
32
+ return;
33
+ }
34
+ return res;
35
+ };
36
+ exports.num = num;
@@ -84,6 +84,7 @@ class VideoSyncHelper extends service_1.IncyclistService {
84
84
  return;
85
85
  }
86
86
  const tsStart = Date.now();
87
+ this.rlvPrev = Object.assign({}, this.rlvStatus);
87
88
  this.tsStart = (_c = this.tsStart) !== null && _c !== void 0 ? _c : Date.now();
88
89
  let updates = [];
89
90
  if (e.bufferedTime !== undefined) {
@@ -95,6 +96,7 @@ class VideoSyncHelper extends service_1.IncyclistService {
95
96
  delete this.bufferedTimeIssue;
96
97
  this.maxSuccessRate = 1;
97
98
  this.maxRate = MAX_PLAYBACK_RATE;
99
+ this.logEvent({ message: 'video forwarded', time });
98
100
  updates.push('rlv:time-reset');
99
101
  }
100
102
  else if (this.rlvStatus.timeRequested !== undefined && time < this.rlvStatus.timeRequested) {
@@ -129,13 +131,13 @@ class VideoSyncHelper extends service_1.IncyclistService {
129
131
  this.logEvent({ message: 'video rate update timeout' });
130
132
  }
131
133
  this.checkIfStalledEnded(time);
132
- this.rlvPrev = Object.assign({}, this.rlvStatus);
133
134
  if (this.isPaused) {
134
135
  this.rlvStatus.ts = Date.now();
135
136
  this.rlvStatus.time = time;
136
137
  this.rlvStatus.rate = 0;
137
138
  return;
138
139
  }
140
+ const prevTime = this.rlvPrev.time;
139
141
  this.rlvStatus.ts = Date.now();
140
142
  this.rlvStatus.time = time;
141
143
  this.rlvStatus.rate = rate;
@@ -150,10 +152,10 @@ class VideoSyncHelper extends service_1.IncyclistService {
150
152
  }
151
153
  updates.push('rlv:lap');
152
154
  }
153
- else if (time < this.rlvStatus.time && time < 10) {
154
- delete this.rlvStatus.lapRequested;
155
- }
156
155
  else {
156
+ if (this.rlvStatus.lapRequested && time < prevTime && time < 10) {
157
+ delete this.rlvStatus.lapRequested;
158
+ }
157
159
  const vt = this.loopMode ? this.getLapTime(time) : (time > this.getTotalTime() ? this.getTotalTime() : time);
158
160
  const mapping = this.getMappingByTime(vt);
159
161
  if (mapping) {
@@ -252,14 +254,21 @@ class VideoSyncHelper extends service_1.IncyclistService {
252
254
  return updates.some(u => u.startsWith('rlv'));
253
255
  };
254
256
  const log = () => {
255
- var _a;
257
+ var _a, _b;
256
258
  if (!canLog())
257
259
  return;
258
- this.logEvent({ message: 'video playback update', updates: updates.join('|'), delta: n(delta, 1), bufferedTime: n(this.bufferedTime, 1), rlvDistance: f(rlvDistance), actDistance: f(actDistance),
259
- rlvTime: n((_a = this.rlvStatus) === null || _a === void 0 ? void 0 : _a.time, 2), rate: n(this.rlvStatus.rate, 2), maxRate: n(this.maxRate, 2), maxSuccessRate: n(this.maxSuccessRate, 2) });
260
+ let debug = {};
261
+ if (Math.abs(delta) > 1000) {
262
+ const mapping = this.getMappingByTime(this.rlvStatus.time);
263
+ const tDelta = (Date.now() - this.rlvStatus.ts) / 1000;
264
+ const s0 = this.rlvStatus.routeDistance;
265
+ const v = this.rlvStatus.speed / 3.6 * ((_a = this.rlvStatus.rate) !== null && _a !== void 0 ? _a : 1);
266
+ debug = { mapping, tDelta, s0, v };
267
+ }
260
268
  if (f(rlvDistance) === '-') {
261
- this.logEvent({ message: 'video debug', videoState: this.rlvStatus, activityState: this.activityStatus });
269
+ debug = Object.assign(Object.assign({}, debug), { rlvStatus: this.rlvStatus, activityStatus: this.activityStatus });
262
270
  }
271
+ this.logEvent(Object.assign({ message: 'video playback update', updates: updates.join('|'), delta: n(delta, 1), bufferedTime: n(this.bufferedTime, 1), rlvDistance: f(rlvDistance), actDistance: f(actDistance), rlvTime: n((_b = this.rlvStatus) === null || _b === void 0 ? void 0 : _b.time, 2), rate: n(this.rlvStatus.rate, 2), maxRate: n(this.maxRate, 2), maxSuccessRate: n(this.maxSuccessRate, 2) }, debug));
263
272
  };
264
273
  if (this.loopMode && actDistance > totalDistance) {
265
274
  actDistance = actDistance % totalDistance;
@@ -282,7 +291,7 @@ class VideoSyncHelper extends service_1.IncyclistService {
282
291
  if (Math.abs(delta) > MAX_DELTA) {
283
292
  const newTime = this.getVideoTimeByPosition(actDistance);
284
293
  if (newTime - this.rlvStatus.time > 1) {
285
- this.logEvent({ message: 'video forwarded', newTime });
294
+ this.logEvent({ message: 'video forward requested', newTime });
286
295
  this.updateTime(newTime);
287
296
  this.prevDelta = delta;
288
297
  }
@@ -355,6 +364,7 @@ class VideoSyncHelper extends service_1.IncyclistService {
355
364
  if (this.rlvStatus.timeRequested)
356
365
  return;
357
366
  this.rlvStatus.timeRequested = time;
367
+ this.rlvStatus.ts = Date.now();
358
368
  delete this.rlvStatus.rateRequested;
359
369
  delete this.rlvStatus.tsLastRateRequest;
360
370
  delete this.tsLastIssue;
@@ -581,17 +591,23 @@ class VideoSyncHelper extends service_1.IncyclistService {
581
591
  if (this.rlvStatus.tsVideoUpdate === undefined)
582
592
  return;
583
593
  const timeSinceLastUpdate = Date.now() - this.rlvStatus.tsVideoUpdate;
584
- if (timeSinceLastUpdate > 2000 && !this.rlvStatus.isStalled) {
594
+ if (timeSinceLastUpdate > 2000 && !this.rlvStatus.isStalled && this.rlvStatus.rate > 0.1 && !this.isPaused && !this.isStopped) {
585
595
  this.rlvStatus.isStalled = true;
596
+ this.rlvStatus.tsStalled = this.rlvStatus.tsVideoUpdate;
586
597
  this.logEvent({ message: 'video stalled', time: this.rlvStatus.time, source: 'timeout' });
587
598
  }
588
599
  }
589
600
  checkIfStalledEnded(time) {
601
+ var _a;
590
602
  if (!this.rlvStatus.isStalled)
591
603
  return;
604
+ if (!this.rlvPrev)
605
+ return;
592
606
  if (time > this.rlvPrev.time) {
593
607
  this.rlvStatus.isStalled = false;
594
- this.logEvent({ message: 'video playback unstalled' });
608
+ const duration = Date.now() - ((_a = this.rlvStatus.tsStalled) !== null && _a !== void 0 ? _a : 0);
609
+ delete this.rlvStatus.tsStalled;
610
+ this.logEvent({ message: 'video playback unstalled', time: this.rlvStatus.time, duration });
595
611
  }
596
612
  }
597
613
  }
@@ -12,6 +12,7 @@ export type RLVPlaybackStatus = {
12
12
  lap: number;
13
13
  lapRequested?: boolean;
14
14
  isStalled?: boolean;
15
+ tsStalled?: number;
15
16
  };
16
17
  export type RLVActivityStatus = {
17
18
  routeDistance: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "incyclist-services",
3
- "version": "1.6.5",
3
+ "version": "1.6.7",
4
4
  "peerDependencies": {
5
5
  "gd-eventlog": "^0.1.26"
6
6
  },
@@ -42,7 +42,7 @@
42
42
  },
43
43
  "dependencies": {
44
44
  "axios": "^1.8.2",
45
- "incyclist-devices": "^2.4.9",
45
+ "incyclist-devices": "^2.4.12",
46
46
  "promise.any": "^2.0.6",
47
47
  "semver": "^7.6.3",
48
48
  "tcx-builder": "^1.1.1",