incyclist-services 1.7.93 → 1.7.95

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.
Files changed (41) hide show
  1. package/lib/cjs/activities/upload/index.js +0 -1
  2. package/lib/cjs/apps/index.js +0 -1
  3. package/lib/cjs/apps/service.js +1 -3
  4. package/lib/cjs/devices/page/service.js +9 -3
  5. package/lib/cjs/devices/page/statemachine.js +14 -4
  6. package/lib/cjs/devices/ride/cyclingMode.js +29 -0
  7. package/lib/cjs/devices/ride/index.js +1 -0
  8. package/lib/cjs/ride/display/service.js +7 -13
  9. package/lib/cjs/ride/route/RLVDisplayService.js +7 -2
  10. package/lib/cjs/ride/workout/WorkoutDisplayService.js +6 -2
  11. package/lib/cjs/routes/list/cards/RouteCard.js +2 -0
  12. package/lib/cjs/video/Mp4BoxParser.js +155 -3
  13. package/lib/cjs/video/VideoConversion.js +1 -1
  14. package/lib/cjs/workouts/ride/page/service.js +13 -2
  15. package/lib/cjs/workouts/ride/service.js +80 -11
  16. package/lib/esm/activities/upload/index.js +0 -1
  17. package/lib/esm/apps/index.js +0 -1
  18. package/lib/esm/apps/service.js +1 -3
  19. package/lib/esm/devices/page/service.js +9 -3
  20. package/lib/esm/devices/page/statemachine.js +14 -4
  21. package/lib/esm/devices/ride/cyclingMode.js +25 -0
  22. package/lib/esm/devices/ride/index.js +1 -0
  23. package/lib/esm/ride/display/service.js +8 -14
  24. package/lib/esm/ride/route/RLVDisplayService.js +7 -2
  25. package/lib/esm/ride/workout/WorkoutDisplayService.js +6 -2
  26. package/lib/esm/routes/list/cards/RouteCard.js +2 -0
  27. package/lib/esm/video/Mp4BoxParser.js +155 -3
  28. package/lib/esm/video/VideoConversion.js +1 -1
  29. package/lib/esm/workouts/ride/page/service.js +13 -2
  30. package/lib/esm/workouts/ride/service.js +81 -12
  31. package/lib/types/activities/upload/index.d.ts +0 -1
  32. package/lib/types/apps/index.d.ts +0 -1
  33. package/lib/types/devices/page/statemachine.d.ts +2 -0
  34. package/lib/types/devices/ride/cyclingMode.d.ts +4 -0
  35. package/lib/types/devices/ride/index.d.ts +1 -0
  36. package/lib/types/video/Mp4BoxParser.d.ts +11 -0
  37. package/lib/types/workouts/ride/page/service.d.ts +2 -1
  38. package/lib/types/workouts/ride/page/types.d.ts +3 -0
  39. package/lib/types/workouts/ride/service.d.ts +10 -1
  40. package/lib/types/workouts/ride/types.d.ts +10 -1
  41. package/package.json +1 -1
@@ -14,7 +14,6 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- __exportStar(require("./velohero"), exports);
18
17
  __exportStar(require("./strava"), exports);
19
18
  __exportStar(require("./intervals"), exports);
20
19
  __exportStar(require("./types"), exports);
@@ -18,4 +18,3 @@ __exportStar(require("./base"), exports);
18
18
  __exportStar(require("./googleMaps"), exports);
19
19
  __exportStar(require("./service"), exports);
20
20
  __exportStar(require("./strava"), exports);
21
- __exportStar(require("./velohero"), exports);
@@ -43,7 +43,6 @@ const settings_1 = require("../settings");
43
43
  const base_1 = require("./base");
44
44
  const IntervalsAppConnection_1 = require("./intervals/IntervalsAppConnection");
45
45
  const komoot_1 = require("./komoot");
46
- const VeloHeroAppConnection_1 = require("./velohero/VeloHeroAppConnection");
47
46
  let AppsService = (() => {
48
47
  let _classDecorators = [types_1.Singleton];
49
48
  let _classDescriptor;
@@ -67,7 +66,7 @@ let AppsService = (() => {
67
66
  __runInitializers(_classThis, _classExtraInitializers);
68
67
  }
69
68
  services = (__runInitializers(this, _instanceExtraInitializers), {
70
- ActivityUpload: ['strava', 'velohero', 'intervals'],
69
+ ActivityUpload: ['strava', 'intervals'],
71
70
  WorkoutUpload: [],
72
71
  WorkoutDownload: ['intervals'],
73
72
  ActivityDownload: [],
@@ -75,7 +74,6 @@ let AppsService = (() => {
75
74
  });
76
75
  serviceMap = [
77
76
  { name: 'Strava', key: 'strava', iconUrl: 'https://static.cdnlogo.com/logos/s/42/strava-wordmark.svg', connection: new base_1.StravaAppConnection() },
78
- { name: 'VeloHero', key: 'velohero', iconUrl: 'images/velo-white.png', connection: new VeloHeroAppConnection_1.VeloHeroAppConnection() },
79
77
  { name: 'Komoot', key: 'komoot', iconUrl: 'https://www.komoot.com/assets/4d8ae313eec53e6e.svg', connection: new komoot_1.KomootAppConnection() },
80
78
  { name: 'Intervals.icu', key: 'intervals', iconUrl: 'images/intervals.png', connection: new IntervalsAppConnection_1.IntervalsAppConnection() },
81
79
  ];
@@ -95,9 +95,15 @@ let DevicesPageService = (() => {
95
95
  if (this.promiseOpen !== undefined) {
96
96
  return this.getPageObserver();
97
97
  }
98
- this.stateMachine.start(() => {
98
+ const onStateMachineUpdate = () => {
99
99
  this.getPageObserver().emit('page-update');
100
- });
100
+ };
101
+ this.stateMachine.start(onStateMachineUpdate);
102
+ if (this.stateMachine.state !== 'Idle') {
103
+ this.logEvent({ message: 'state machine was not in expected state on open, resetting', page: 'Pairing', state: this.stateMachine.state });
104
+ this.stateMachine.stop();
105
+ this.stateMachine.start(onStateMachineUpdate);
106
+ }
101
107
  this.promiseOpen = new Promise((done) => {
102
108
  this.start()
103
109
  .catch((err) => { this.logError(err, 'openPage'); })
@@ -321,7 +327,7 @@ let DevicesPageService = (() => {
321
327
  this.updatePage();
322
328
  }
323
329
  async closeDeviceSelection(enabled) {
324
- this.logEvent({ message: 'capability closed', capability: this.openedCapability });
330
+ this.logEvent({ message: 'capability closed', capability: this.openedCapability, enabled, caller: new Error().stack });
325
331
  if (!enabled) {
326
332
  const all = this.state.capabilities ?? [];
327
333
  const requested = all.find(c => c.capability === this.openedCapability);
@@ -68,6 +68,8 @@ let PairingPageStateMachine = (() => {
68
68
  selectStateChangeCallback;
69
69
  retryTimeout;
70
70
  selectionTimeout;
71
+ pairingPromise;
72
+ scanningPromise;
71
73
  constructor() {
72
74
  this.logger = new gd_eventlog_1.EventLogger('PairingPageState');
73
75
  }
@@ -99,6 +101,8 @@ let PairingPageStateMachine = (() => {
99
101
  this.unregisterServiceEventHandlers();
100
102
  delete this.stateChangeCallback;
101
103
  this.resetTimeouts();
104
+ delete this.pairingPromise;
105
+ delete this.scanningPromise;
102
106
  this.setState('Closed');
103
107
  this.logIncomingEvent('stop', prev, 'Closed');
104
108
  }
@@ -199,14 +203,20 @@ let PairingPageStateMachine = (() => {
199
203
  this.resetTimeouts();
200
204
  this.setState('Done');
201
205
  }
202
- else {
206
+ else if (!this.pairingPromise) {
203
207
  this.setState('Pairing');
204
- service.startPairing(adapters, {});
208
+ this.pairingPromise = Promise.resolve(service.startPairing(adapters, {}))
209
+ .catch(err => { this.logError(err, 'startPairing'); })
210
+ .finally(() => { delete this.pairingPromise; });
205
211
  }
206
212
  }
207
213
  else if (this.selectState === 'Closed' || this.selectState === 'Active') {
208
- this.setState('Scanning');
209
- service.startScanning(adapters, {});
214
+ if (!this.scanningPromise) {
215
+ this.setState('Scanning');
216
+ this.scanningPromise = Promise.resolve(service.startScanning(adapters, {}))
217
+ .catch(err => { this.logError(err, 'startScanning'); })
218
+ .finally(() => { delete this.scanningPromise; });
219
+ }
210
220
  }
211
221
  if (this.state !== oldState)
212
222
  this.logger.logEvent({ message: 'state changed', stateTransition: { from: oldState, to: this.state } });
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isVirtualShiftingEnabled = isVirtualShiftingEnabled;
4
+ exports.getLoadButtonMode = getLoadButtonMode;
5
+ function isVirtualShiftingEnabled(mode) {
6
+ try {
7
+ if (!mode)
8
+ return false;
9
+ if (mode.isSIM()) {
10
+ const virtshiftMode = mode.getSetting('virtshift');
11
+ return (virtshiftMode === 'Mixed' || virtshiftMode === 'Incyclist' || virtshiftMode === 'SmartTrainer' || virtshiftMode === 'Enabled');
12
+ }
13
+ if (mode.isResistance()) {
14
+ return true;
15
+ }
16
+ return false;
17
+ }
18
+ catch {
19
+ return false;
20
+ }
21
+ }
22
+ function getLoadButtonMode(mode) {
23
+ if (!mode)
24
+ return 'power';
25
+ if (mode.isSIM() || mode.isResistance()) {
26
+ return isVirtualShiftingEnabled(mode) ? 'gear' : 'hidden';
27
+ }
28
+ return 'power';
29
+ }
@@ -15,3 +15,4 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./service"), exports);
18
+ __exportStar(require("./cyclingMode"), exports);
@@ -432,8 +432,11 @@ let RideDisplayService = (() => {
432
432
  this.observer?.stop({ immediately: props.noStateUpdates });
433
433
  if (props.noStateUpdates)
434
434
  delete this.observer;
435
- else
436
- (0, utils_1.waitNextTick)().then(() => delete this.observer);
435
+ else {
436
+ const observerToClear = this.observer;
437
+ (0, utils_1.waitNextTick)().then(() => { if (this.observer === observerToClear)
438
+ delete this.observer; });
439
+ }
437
440
  if (prevState === 'Finished' || prevState === 'Idle' || prevState === 'Closing') {
438
441
  this.state = 'Idle';
439
442
  return;
@@ -553,7 +556,7 @@ let RideDisplayService = (() => {
553
556
  const { time, speed, routeDistance, distance } = currentValues;
554
557
  const data = { time, speed, routeDistance, distance };
555
558
  this.getRideModeService().onActivityUpdate(data, currentValues);
556
- this.observer.emit('data-update', data, currentValues);
559
+ this.observer?.emit('data-update', data, currentValues);
557
560
  }
558
561
  }
559
562
  onWorkoutStopped() {
@@ -1085,16 +1088,7 @@ let RideDisplayService = (() => {
1085
1088
  isVirtualShiftingEnabled() {
1086
1089
  try {
1087
1090
  const mode = this.getDeviceRide().getCyclingMode();
1088
- if (!mode)
1089
- return false;
1090
- if (mode.isSIM()) {
1091
- const virtshiftMode = mode.getSetting('virtshift');
1092
- return (virtshiftMode === 'Mixed' || virtshiftMode === 'Incyclist' || virtshiftMode === 'SmartTrainer' || virtshiftMode === 'Enabled');
1093
- }
1094
- if (mode.isResistance()) {
1095
- return true;
1096
- }
1097
- return false;
1091
+ return (0, devices_1.isVirtualShiftingEnabled)(mode);
1098
1092
  }
1099
1093
  catch (err) {
1100
1094
  this.logError(err, 'isVirtualShiftingEnabled');
@@ -374,6 +374,10 @@ let RLVDisplayService = (() => {
374
374
  if (time !== undefined) {
375
375
  this.currentVideo.buffering = `${time}s`;
376
376
  this.currentVideo.bufferTime = time;
377
+ if (time > 200) {
378
+ this.logEvent({ message: 'video conversion >200s' });
379
+ this.onVideoLoaded(0);
380
+ }
377
381
  }
378
382
  else if (progress !== undefined)
379
383
  this.currentVideo.buffering = `${progress}%`;
@@ -390,11 +394,12 @@ let RLVDisplayService = (() => {
390
394
  onVideoLoadError(error, video = this.currentVideo) {
391
395
  video.loaded = false;
392
396
  video.error = this.buildVideoError(error, video);
397
+ const isCurrentVideo = video === this.currentVideo;
393
398
  if (video.isInitial) {
394
- this.logEvent({ message: 'could not load video', video: this.getVideoUrl(video), error: error.message, errorCode: this.getCode(error) });
399
+ this.logEvent({ message: 'could not load video', video: this.getVideoUrl(video), error: error.message, errorCode: this.getCode(error), isCurrentVideo });
395
400
  }
396
401
  else {
397
- this.logEvent({ message: 'could not load next video', video: this.getVideoUrl(video), error: error.message, errorCode: this.getCode(error) });
402
+ this.logEvent({ message: 'could not load next video', video: this.getVideoUrl(video), error: error.message, errorCode: this.getCode(error), isCurrentVideo });
398
403
  const errIdx = this.videos.indexOf(video);
399
404
  if (errIdx !== -1) {
400
405
  this.videos.splice(errIdx);
@@ -35,6 +35,7 @@ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn,
35
35
  };
36
36
  Object.defineProperty(exports, "__esModule", { value: true });
37
37
  exports.WorkoutDisplayService = void 0;
38
+ const devices_1 = require("../../devices");
38
39
  const workouts_1 = require("../../workouts");
39
40
  const base_1 = require("../base/base");
40
41
  const decorators_1 = require("../../base/decorators");
@@ -56,6 +57,9 @@ let WorkoutDisplayService = (() => {
56
57
  prevPowerTs = __runInitializers(this, _instanceExtraInitializers);
57
58
  buildRequest(props = {}) {
58
59
  if (props?.limits) {
60
+ const mode = this.getDeviceRide().getCyclingMode();
61
+ if ((0, devices_1.getLoadButtonMode)(mode) !== 'power')
62
+ return {};
59
63
  return props.limits;
60
64
  }
61
65
  else
@@ -76,12 +80,12 @@ let WorkoutDisplayService = (() => {
76
80
  }
77
81
  }
78
82
  getLogProps() {
79
- const workout = this.getWorkoutList().getSelected();
83
+ const workout = this.getWorkoutList().getSelected() ?? this.getWorkoutRide().getWorkout();
80
84
  const settings = this.getWorkoutList().getStartSettings() ?? {};
81
85
  const bikeProps = this.getBikeLogProps();
82
86
  return {
83
87
  mode: 'workout',
84
- workout: workout.name,
88
+ workout: workout?.name,
85
89
  ...settings,
86
90
  ...bikeProps
87
91
  };
@@ -165,6 +165,8 @@ let RouteCard = (() => {
165
165
  path = normalizedUrl.replace('file://', '');
166
166
  if (normalizedUrl.startsWith('video:///'))
167
167
  path = normalizedUrl.replace('video://', '');
168
+ if (path && /^\/[A-Za-z]:[\\/]/.test(path))
169
+ path = path.slice(1);
168
170
  if (path) {
169
171
  return await this.fileExists(path);
170
172
  }
@@ -65,6 +65,152 @@ const parseStsdCodec = (buf, stsdBox) => {
65
65
  return undefined;
66
66
  return buf.toString('ascii', entryOffset + 4, entryOffset + 8);
67
67
  };
68
+ const CHROMA_FORMAT_NAMES = {
69
+ 0: '4:0:0 (monochrome)', 1: '4:2:0', 2: '4:2:2', 3: '4:4:4'
70
+ };
71
+ const findSampleEntryChildBoxes = (buf, stsdBox) => {
72
+ const entryOffset = stsdBox.bodyStart + 8;
73
+ if (entryOffset + 4 > buf.length)
74
+ return [];
75
+ const entrySize = buf.readUInt32BE(entryOffset);
76
+ const childStart = entryOffset + 86;
77
+ const childEnd = Math.min(entryOffset + entrySize, buf.length);
78
+ if (childStart >= childEnd)
79
+ return [];
80
+ return findBoxes(buf, childStart, childEnd);
81
+ };
82
+ const AVC_PROFILE_NAMES = {
83
+ 66: 'Baseline', 77: 'Main', 88: 'Extended', 100: 'High',
84
+ 110: 'High 10', 122: 'High 4:2:2', 144: 'High 4:4:4', 244: 'High 4:4:4 Predictive'
85
+ };
86
+ const AVC_HIGH_FAMILY_PROFILES = new Set([100, 110, 122, 144, 244]);
87
+ const parseAvcC = (buf, avcCBox) => {
88
+ const start = avcCBox.bodyStart;
89
+ if (start + 4 > buf.length)
90
+ return undefined;
91
+ const profileIdc = buf.readUInt8(start + 1);
92
+ const levelIdc = buf.readUInt8(start + 3);
93
+ const details = {
94
+ profile: AVC_PROFILE_NAMES[profileIdc] ?? `unknown (${profileIdc})`,
95
+ profileId: profileIdc,
96
+ level: (levelIdc / 10).toFixed(1)
97
+ };
98
+ if (!AVC_HIGH_FAMILY_PROFILES.has(profileIdc))
99
+ return details;
100
+ try {
101
+ let offset = start + 5;
102
+ if (offset >= buf.length)
103
+ return details;
104
+ const numSps = buf.readUInt8(offset) & 0x1f;
105
+ offset += 1;
106
+ for (let i = 0; i < numSps; i++) {
107
+ if (offset + 2 > buf.length)
108
+ return details;
109
+ offset += 2 + buf.readUInt16BE(offset);
110
+ }
111
+ if (offset >= buf.length)
112
+ return details;
113
+ const numPps = buf.readUInt8(offset);
114
+ offset += 1;
115
+ for (let i = 0; i < numPps; i++) {
116
+ if (offset + 2 > buf.length)
117
+ return details;
118
+ offset += 2 + buf.readUInt16BE(offset);
119
+ }
120
+ if (offset + 3 > buf.length)
121
+ return details;
122
+ details.chromaFormat = CHROMA_FORMAT_NAMES[buf.readUInt8(offset) & 0x03];
123
+ details.bitDepthLuma = (buf.readUInt8(offset + 1) & 0x07) + 8;
124
+ details.bitDepthChroma = (buf.readUInt8(offset + 2) & 0x07) + 8;
125
+ }
126
+ catch { }
127
+ return details;
128
+ };
129
+ const HEVC_PROFILE_NAMES = {
130
+ 1: 'Main', 2: 'Main 10', 3: 'Main Still Picture', 4: 'Range Extensions',
131
+ 5: 'High Throughput', 6: 'Multiview Main', 7: 'Scalable Main', 8: '3D Main',
132
+ 9: 'Screen Content Coding', 10: 'Scalable Range Extensions'
133
+ };
134
+ const parseHvcC = (buf, hvcCBox) => {
135
+ const start = hvcCBox.bodyStart;
136
+ if (start + 19 > buf.length)
137
+ return undefined;
138
+ const byte1 = buf.readUInt8(start + 1);
139
+ const generalProfileIdc = byte1 & 0x1f;
140
+ const generalTierFlag = (byte1 >> 5) & 0x01;
141
+ const generalLevelIdc = buf.readUInt8(start + 12);
142
+ const chromaFormatIdc = buf.readUInt8(start + 16) & 0x03;
143
+ return {
144
+ profile: HEVC_PROFILE_NAMES[generalProfileIdc] ?? `unknown (${generalProfileIdc})`,
145
+ profileId: generalProfileIdc,
146
+ tier: generalTierFlag ? 'High' : 'Main',
147
+ level: (generalLevelIdc / 30).toFixed(1),
148
+ chromaFormat: CHROMA_FORMAT_NAMES[chromaFormatIdc],
149
+ bitDepthLuma: (buf.readUInt8(start + 17) & 0x07) + 8,
150
+ bitDepthChroma: (buf.readUInt8(start + 18) & 0x07) + 8
151
+ };
152
+ };
153
+ const AV1_PROFILE_NAMES = { 0: 'Main', 1: 'High', 2: 'Professional' };
154
+ const av1Level = (levelIdx) => `${2 + Math.floor(levelIdx / 4)}.${levelIdx % 4}`;
155
+ const av1BitDepth = (highBitdepth, twelveBit) => {
156
+ if (twelveBit)
157
+ return 12;
158
+ return highBitdepth ? 10 : 8;
159
+ };
160
+ const av1ChromaFormat = (monochrome, subsamplingX, subsamplingY) => {
161
+ if (monochrome)
162
+ return CHROMA_FORMAT_NAMES[0];
163
+ if (subsamplingX && subsamplingY)
164
+ return '4:2:0';
165
+ if (subsamplingX && !subsamplingY)
166
+ return '4:2:2';
167
+ return '4:4:4';
168
+ };
169
+ const parseAv1C = (buf, av1CBox) => {
170
+ const start = av1CBox.bodyStart;
171
+ if (start + 3 > buf.length)
172
+ return undefined;
173
+ const byte1 = buf.readUInt8(start + 1);
174
+ const seqProfile = (byte1 >> 5) & 0x07;
175
+ const seqLevelIdx0 = byte1 & 0x1f;
176
+ const byte2 = buf.readUInt8(start + 2);
177
+ const seqTier0 = (byte2 >> 7) & 0x01;
178
+ const highBitdepth = (byte2 >> 6) & 0x01;
179
+ const twelveBit = (byte2 >> 5) & 0x01;
180
+ const monochrome = (byte2 >> 4) & 0x01;
181
+ const chromaSubsamplingX = (byte2 >> 3) & 0x01;
182
+ const chromaSubsamplingY = (byte2 >> 2) & 0x01;
183
+ const bitDepth = av1BitDepth(highBitdepth, twelveBit);
184
+ const chromaFormat = av1ChromaFormat(monochrome, chromaSubsamplingX, chromaSubsamplingY);
185
+ return {
186
+ profile: AV1_PROFILE_NAMES[seqProfile] ?? `unknown (${seqProfile})`,
187
+ profileId: seqProfile,
188
+ tier: seqTier0 ? 'High' : 'Main',
189
+ level: av1Level(seqLevelIdx0),
190
+ chromaFormat,
191
+ bitDepthLuma: bitDepth,
192
+ bitDepthChroma: bitDepth,
193
+ monochrome: !!monochrome
194
+ };
195
+ };
196
+ const parseCodecDetails = (buf, codec, stsdBox) => {
197
+ if (!codec)
198
+ return undefined;
199
+ const childBoxes = findSampleEntryChildBoxes(buf, stsdBox);
200
+ if (codec === 'avc1' || codec === 'avc3') {
201
+ const avcCBox = findBox(childBoxes, 'avcC');
202
+ return avcCBox ? parseAvcC(buf, avcCBox) : undefined;
203
+ }
204
+ if (codec === 'hev1' || codec === 'hvc1') {
205
+ const hvcCBox = findBox(childBoxes, 'hvcC');
206
+ return hvcCBox ? parseHvcC(buf, hvcCBox) : undefined;
207
+ }
208
+ if (codec === 'av01') {
209
+ const av1CBox = findBox(childBoxes, 'av1C');
210
+ return av1CBox ? parseAv1C(buf, av1CBox) : undefined;
211
+ }
212
+ return undefined;
213
+ };
68
214
  const parseTkhdDimensions = (buf, tkhdBox) => {
69
215
  if (tkhdBox.bodyStart >= buf.length)
70
216
  return {};
@@ -88,18 +234,22 @@ const parseTrak = (buf, trakBox) => {
88
234
  const minfBox = findBox(mdiaChildren, 'minf');
89
235
  const handlerType = hdlrBox ? parseHandlerType(buf, hdlrBox) : undefined;
90
236
  let codec;
237
+ let codecDetails;
91
238
  if (minfBox) {
92
239
  const minfChildren = findBoxes(buf, minfBox.bodyStart, minfBox.bodyEnd);
93
240
  const stblBox = findBox(minfChildren, 'stbl');
94
241
  if (stblBox) {
95
242
  const stblChildren = findBoxes(buf, stblBox.bodyStart, stblBox.bodyEnd);
96
243
  const stsdBox = findBox(stblChildren, 'stsd');
97
- if (stsdBox)
244
+ if (stsdBox) {
98
245
  codec = parseStsdCodec(buf, stsdBox);
246
+ if (handlerType === 'vide')
247
+ codecDetails = parseCodecDetails(buf, codec, stsdBox);
248
+ }
99
249
  }
100
250
  }
101
251
  const dimensions = (handlerType === 'vide' && tkhdBox) ? parseTkhdDimensions(buf, tkhdBox) : {};
102
- return { handlerType, codec, ...dimensions };
252
+ return { handlerType, codec, codecDetails, ...dimensions };
103
253
  };
104
254
  const parseMp4Boxes = (headBuffer, tailBuffer) => {
105
255
  const result = { moovLocation: 'not-found', incomplete: true };
@@ -130,13 +280,15 @@ const applyMoov = (buf, moovBox, result) => {
130
280
  const moovChildren = findBoxes(buf, moovBox.bodyStart, moovBox.bodyEnd);
131
281
  const trakBoxes = moovChildren.filter(b => b.type === 'trak');
132
282
  trakBoxes.forEach(trakBox => {
133
- const { handlerType, codec, width, height } = parseTrak(buf, trakBox);
283
+ const { handlerType, codec, width, height, codecDetails } = parseTrak(buf, trakBox);
134
284
  if (handlerType === 'vide') {
135
285
  result.videoCodec = codec;
136
286
  if (width)
137
287
  result.width = width;
138
288
  if (height)
139
289
  result.height = height;
290
+ if (codecDetails)
291
+ result.codecDetails = codecDetails;
140
292
  }
141
293
  else if (handlerType === 'soun') {
142
294
  result.audioCodec = codec;
@@ -135,7 +135,7 @@ let VideoConversion = (() => {
135
135
  const frames = mapping?.length > 0 ? Math.max(...mapping.map(m => m.frame)) : 0;
136
136
  const framerate = this.route.details?.video?.framerate;
137
137
  const time = framerate ? Math.round(info.frames / framerate) : parseTime(info.timemark);
138
- const pct = frames ? Math.floor(info.frames / frames * 100) : undefined;
138
+ const pct = (frames && !isNaN(frames)) ? Math.floor(info.frames / frames * 100) : undefined;
139
139
  const cpu = this.getCpuStats(info);
140
140
  if (frames || time) {
141
141
  this.observer.emit('convert-progress', pct, info.frames, time);
@@ -172,7 +172,8 @@ let WorkoutRidePageService = (() => {
172
172
  steps: this.buildUpcomingSteps(current, wo.ftp),
173
173
  dashboard: this.buildDashboardLine(wo),
174
174
  gestureHint: this.buildGestureHint(base.startOverlayProps === null),
175
- loadIncrement: this.getLoadIncrement()
175
+ loadIncrement: this.getLoadIncrement(),
176
+ loadButtonMode: wo.loadButtonMode ?? 'power'
176
177
  };
177
178
  }
178
179
  catch (err) {
@@ -334,6 +335,15 @@ let WorkoutRidePageService = (() => {
334
335
  return undefined;
335
336
  }
336
337
  }
338
+ getLoadButtonMode() {
339
+ try {
340
+ return this.getWorkoutRide().getLoadButtonMode();
341
+ }
342
+ catch (err) {
343
+ this.logError(err, 'getLoadButtonMode');
344
+ return 'power';
345
+ }
346
+ }
337
347
  onRideStateUpdate(state) {
338
348
  this.subscribeToWorkoutObserver();
339
349
  switch (state) {
@@ -512,7 +522,8 @@ let WorkoutRidePageService = (() => {
512
522
  steps: { previous: null, current: null, upcoming: [], hasMore: false },
513
523
  dashboard: { text: '', mode: null },
514
524
  gestureHint: null,
515
- loadIncrement: DEFAULT_LOAD_INCREMENT
525
+ loadIncrement: DEFAULT_LOAD_INCREMENT,
526
+ loadButtonMode: 'power'
516
527
  };
517
528
  }
518
529
  updatePageDisplay() {
@@ -57,14 +57,17 @@ let WorkoutRide = (() => {
57
57
  let _instanceExtraInitializers = [];
58
58
  let _getUserSettings_decorators;
59
59
  let _getWorkoutList_decorators;
60
+ let _getDeviceRide_decorators;
60
61
  var WorkoutRide = class extends _classSuper {
61
62
  static { _classThis = this; }
62
63
  static {
63
64
  const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
64
65
  _getUserSettings_decorators = [decorators_1.Injectable];
65
66
  _getWorkoutList_decorators = [decorators_1.Injectable];
67
+ _getDeviceRide_decorators = [decorators_1.Injectable];
66
68
  __esDecorate(this, null, _getUserSettings_decorators, { kind: "method", name: "getUserSettings", static: false, private: false, access: { has: obj => "getUserSettings" in obj, get: obj => obj.getUserSettings }, metadata: _metadata }, null, _instanceExtraInitializers);
67
69
  __esDecorate(this, null, _getWorkoutList_decorators, { kind: "method", name: "getWorkoutList", static: false, private: false, access: { has: obj => "getWorkoutList" in obj, get: obj => obj.getWorkoutList }, metadata: _metadata }, null, _instanceExtraInitializers);
70
+ __esDecorate(this, null, _getDeviceRide_decorators, { kind: "method", name: "getDeviceRide", static: false, private: false, access: { has: obj => "getDeviceRide" in obj, get: obj => obj.getDeviceRide }, metadata: _metadata }, null, _instanceExtraInitializers);
68
71
  __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
69
72
  WorkoutRide = _classThis = _classDescriptor.value;
70
73
  if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
@@ -269,16 +272,68 @@ let WorkoutRide = (() => {
269
272
  this.logError(err, 'backward', { ts, limits });
270
273
  }
271
274
  }
275
+ isPowerRangeAdjustable(delta) {
276
+ if (this.currentLimits?.minPower === this.currentLimits?.maxPower)
277
+ return false;
278
+ return delta >= 0
279
+ ? this.currentLimits?.targetPower < this.currentLimits?.maxPower
280
+ : this.currentLimits?.targetPower > this.currentLimits?.minPower;
281
+ }
282
+ getPowerRangeDeltaVal(magnitude) {
283
+ if (!this.settings?.ftp)
284
+ return magnitude;
285
+ return magnitude === 1 ? 5 : 50;
286
+ }
287
+ getPowerRangeAdjustmentWatts(magnitude, increase) {
288
+ const nominal = this.getPowerRangeDeltaVal(magnitude);
289
+ const headroom = increase
290
+ ? this.currentLimits.maxPower - this.currentLimits.targetPower
291
+ : this.currentLimits.targetPower - this.currentLimits.minPower;
292
+ return Math.min(nominal, headroom);
293
+ }
294
+ getLoadButtonMode() {
295
+ try {
296
+ const mode = this.getDeviceRide().getCyclingMode();
297
+ return (0, devices_1.getLoadButtonMode)(mode);
298
+ }
299
+ catch (err) {
300
+ this.logError(err, 'getLoadButtonMode');
301
+ return 'power';
302
+ }
303
+ }
304
+ getGearButtonLabels() {
305
+ return { inc5: '+5', inc1: '+1', dec1: '-1', dec5: '-5' };
306
+ }
307
+ getLoadButtonLabels() {
308
+ const label = (magnitude, increase) => {
309
+ const sign = increase ? '+' : '-';
310
+ const delta = increase ? magnitude : -magnitude;
311
+ return this.isPowerRangeAdjustable(delta)
312
+ ? `${sign}${this.getPowerRangeAdjustmentWatts(magnitude, increase)}W`
313
+ : `${sign}${magnitude}%`;
314
+ };
315
+ return {
316
+ inc5: label(5, true),
317
+ inc1: label(1, true),
318
+ dec1: label(1, false),
319
+ dec5: label(5, false),
320
+ };
321
+ }
272
322
  powerUp(delta) {
273
323
  if (delta < 0)
274
324
  return this.powerDown(-delta);
275
325
  this.logEvent({ message: 'workout power up', delta });
276
326
  try {
277
- if (this.currentLimits?.minPower !== this.currentLimits?.maxPower && this.currentLimits?.targetPower < this.currentLimits?.maxPower) {
278
- let deltaVal = delta;
279
- if (this.settings?.ftp) {
280
- deltaVal = delta === 1 ? 5 : 50;
281
- }
327
+ const loadButtonMode = this.getLoadButtonMode();
328
+ if (loadButtonMode === 'hidden')
329
+ return undefined;
330
+ if (loadButtonMode === 'gear') {
331
+ this.gearChange(delta);
332
+ this.logEvent({ message: 'workout gear shift', gearDelta: delta });
333
+ return { type: 'gear', value: delta };
334
+ }
335
+ if (this.isPowerRangeAdjustable(delta)) {
336
+ const deltaVal = this.getPowerRangeDeltaVal(delta);
282
337
  this.currentLimits.targetPower = Math.min(this.currentLimits.targetPower + deltaVal, this.currentLimits.maxPower);
283
338
  this.logEvent({ message: 'workout target power adjusted', targetPower: this.currentLimits.targetPower });
284
339
  this.emit('update', this.getDashboardDisplayProperties());
@@ -304,11 +359,16 @@ let WorkoutRide = (() => {
304
359
  powerDown(delta) {
305
360
  this.logEvent({ message: 'workout power down', delta });
306
361
  try {
307
- if (this.currentLimits?.minPower !== this.currentLimits?.maxPower && this.currentLimits?.targetPower > this.currentLimits?.minPower) {
308
- let deltaVal = delta;
309
- if (this.settings?.ftp) {
310
- deltaVal = delta === 1 ? 5 : 50;
311
- }
362
+ const loadButtonMode = this.getLoadButtonMode();
363
+ if (loadButtonMode === 'hidden')
364
+ return undefined;
365
+ if (loadButtonMode === 'gear') {
366
+ this.gearChange(-delta);
367
+ this.logEvent({ message: 'workout gear shift', gearDelta: -delta });
368
+ return { type: 'gear', value: -delta };
369
+ }
370
+ if (this.isPowerRangeAdjustable(-delta)) {
371
+ const deltaVal = this.getPowerRangeDeltaVal(delta);
312
372
  this.currentLimits.targetPower = Math.max(this.currentLimits.targetPower - deltaVal, this.currentLimits.minPower);
313
373
  this.logEvent({ message: 'workout target power adjusted', targetPower: this.currentLimits.targetPower });
314
374
  this.emit('update', this.getDashboardDisplayProperties());
@@ -331,6 +391,9 @@ let WorkoutRide = (() => {
331
391
  return undefined;
332
392
  }
333
393
  }
394
+ gearChange(gearDelta) {
395
+ this.getDeviceRide().sendUpdate({ gearDelta });
396
+ }
334
397
  toggleCyclingMode() {
335
398
  const deviceRide = (0, devices_1.useDeviceRide)();
336
399
  deviceRide.toggleCyclingMode();
@@ -344,6 +407,7 @@ let WorkoutRide = (() => {
344
407
  const { start, stop } = this.getZoomParameters(this.trainingTime);
345
408
  const title = this.getStepTitle(this.trainingTime);
346
409
  const canShowBackward = Math.round((this.trainingTime ?? 0)) > 0;
410
+ const loadButtonMode = this.getLoadButtonMode();
347
411
  const props = {
348
412
  workout: this.workout, title,
349
413
  ftp: this.settings.ftp,
@@ -351,7 +415,9 @@ let WorkoutRide = (() => {
351
415
  start, stop,
352
416
  mode: this.getCyclingModeText(),
353
417
  canShowBackward,
354
- canShowForward: true
418
+ canShowForward: true,
419
+ loadButtonMode,
420
+ loadButtons: loadButtonMode === 'power' ? this.getLoadButtonLabels() : this.getGearButtonLabels()
355
421
  };
356
422
  return props;
357
423
  }
@@ -618,6 +684,9 @@ let WorkoutRide = (() => {
618
684
  getWorkoutList() {
619
685
  return (0, list_1.useWorkoutList)();
620
686
  }
687
+ getDeviceRide() {
688
+ return (0, devices_1.useDeviceRide)();
689
+ }
621
690
  };
622
691
  return WorkoutRide = _classThis;
623
692
  })();
@@ -1,4 +1,3 @@
1
- export * from './velohero';
2
1
  export * from './strava';
3
2
  export * from './intervals';
4
3
  export * from './types';