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.
- package/lib/cjs/activities/upload/index.js +0 -1
- package/lib/cjs/apps/index.js +0 -1
- package/lib/cjs/apps/service.js +1 -3
- package/lib/cjs/devices/page/service.js +9 -3
- package/lib/cjs/devices/page/statemachine.js +14 -4
- package/lib/cjs/devices/ride/cyclingMode.js +29 -0
- package/lib/cjs/devices/ride/index.js +1 -0
- package/lib/cjs/ride/display/service.js +7 -13
- package/lib/cjs/ride/route/RLVDisplayService.js +7 -2
- package/lib/cjs/ride/workout/WorkoutDisplayService.js +6 -2
- package/lib/cjs/routes/list/cards/RouteCard.js +2 -0
- package/lib/cjs/video/Mp4BoxParser.js +155 -3
- package/lib/cjs/video/VideoConversion.js +1 -1
- package/lib/cjs/workouts/ride/page/service.js +13 -2
- package/lib/cjs/workouts/ride/service.js +80 -11
- package/lib/esm/activities/upload/index.js +0 -1
- package/lib/esm/apps/index.js +0 -1
- package/lib/esm/apps/service.js +1 -3
- package/lib/esm/devices/page/service.js +9 -3
- package/lib/esm/devices/page/statemachine.js +14 -4
- package/lib/esm/devices/ride/cyclingMode.js +25 -0
- package/lib/esm/devices/ride/index.js +1 -0
- package/lib/esm/ride/display/service.js +8 -14
- package/lib/esm/ride/route/RLVDisplayService.js +7 -2
- package/lib/esm/ride/workout/WorkoutDisplayService.js +6 -2
- package/lib/esm/routes/list/cards/RouteCard.js +2 -0
- package/lib/esm/video/Mp4BoxParser.js +155 -3
- package/lib/esm/video/VideoConversion.js +1 -1
- package/lib/esm/workouts/ride/page/service.js +13 -2
- package/lib/esm/workouts/ride/service.js +81 -12
- package/lib/types/activities/upload/index.d.ts +0 -1
- package/lib/types/apps/index.d.ts +0 -1
- package/lib/types/devices/page/statemachine.d.ts +2 -0
- package/lib/types/devices/ride/cyclingMode.d.ts +4 -0
- package/lib/types/devices/ride/index.d.ts +1 -0
- package/lib/types/video/Mp4BoxParser.d.ts +11 -0
- package/lib/types/workouts/ride/page/service.d.ts +2 -1
- package/lib/types/workouts/ride/page/types.d.ts +3 -0
- package/lib/types/workouts/ride/service.d.ts +10 -1
- package/lib/types/workouts/ride/types.d.ts +10 -1
- 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);
|
package/lib/cjs/apps/index.js
CHANGED
package/lib/cjs/apps/service.js
CHANGED
|
@@ -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', '
|
|
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
|
-
|
|
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.
|
|
209
|
-
|
|
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
|
+
}
|
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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
|
})();
|