incyclist-services 1.7.91 → 1.7.93
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/base/api/fitconvert/fitconvert.js +2 -2
- package/lib/cjs/activities/base/repo/db.js +2 -2
- package/lib/cjs/activities/base/utils/activity.js +1 -0
- package/lib/cjs/apps/base/api/velohero/api.js +1 -1
- package/lib/cjs/apps/intervals/IntervalsAppConnection.js +1 -1
- package/lib/cjs/apps/komoot/KomootAppConnection.js +1 -1
- package/lib/cjs/apps/strava/StravaAppConnection.js +1 -1
- package/lib/cjs/apps/velohero/VeloHeroAppConnection.js +1 -1
- package/lib/cjs/autoupdate/api.js +15 -5
- package/lib/cjs/coaches/service.js +1 -2
- package/lib/cjs/devices/ride/service.js +1 -1
- package/lib/cjs/maps/MapArea/MapArea.js +1 -2
- package/lib/cjs/maps/MapArea/service.js +1 -0
- package/lib/cjs/ride/display/service.js +1 -1
- package/lib/cjs/ride/route/FreeRideDisplayService.js +1 -2
- package/lib/cjs/ride/route/RLVDisplayService.js +7 -17
- package/lib/cjs/routes/base/parsers/incyclist.js +1 -1
- package/lib/cjs/routes/base/parsers/xml.js +1 -1
- package/lib/cjs/routes/base/utils/points.js +3 -3
- package/lib/cjs/routes/list/cards/RouteCard.js +5 -4
- package/lib/cjs/routes/list/loaders/db.js +1 -1
- package/lib/cjs/ui/service.js +8 -3
- package/lib/cjs/video/Mp4BoxParser.js +145 -0
- package/lib/cjs/video/VideoConversion.js +1 -1
- package/lib/cjs/video/index.js +2 -0
- package/lib/cjs/video/resolvePlaybackUrl.js +23 -0
- package/lib/cjs/workouts/list/cards/WorkoutCard.js +1 -1
- package/lib/cjs/workouts/list/loaders/db.js +1 -1
- package/lib/esm/activities/base/api/fitconvert/fitconvert.js +2 -2
- package/lib/esm/activities/base/repo/db.js +2 -2
- package/lib/esm/activities/base/utils/activity.js +1 -0
- package/lib/esm/apps/base/api/velohero/api.js +1 -1
- package/lib/esm/apps/intervals/IntervalsAppConnection.js +1 -1
- package/lib/esm/apps/komoot/KomootAppConnection.js +1 -1
- package/lib/esm/apps/strava/StravaAppConnection.js +1 -1
- package/lib/esm/apps/velohero/VeloHeroAppConnection.js +1 -1
- package/lib/esm/autoupdate/api.js +15 -5
- package/lib/esm/coaches/service.js +1 -2
- package/lib/esm/devices/ride/service.js +1 -1
- package/lib/esm/maps/MapArea/MapArea.js +1 -2
- package/lib/esm/maps/MapArea/service.js +1 -0
- package/lib/esm/ride/display/service.js +1 -1
- package/lib/esm/ride/route/FreeRideDisplayService.js +1 -2
- package/lib/esm/ride/route/RLVDisplayService.js +8 -18
- package/lib/esm/routes/base/parsers/incyclist.js +1 -1
- package/lib/esm/routes/base/parsers/xml.js +1 -1
- package/lib/esm/routes/base/utils/points.js +3 -3
- package/lib/esm/routes/list/cards/RouteCard.js +5 -4
- package/lib/esm/routes/list/loaders/db.js +1 -1
- package/lib/esm/ui/service.js +8 -3
- package/lib/esm/video/Mp4BoxParser.js +141 -0
- package/lib/esm/video/VideoConversion.js +1 -1
- package/lib/esm/video/index.js +2 -0
- package/lib/esm/video/resolvePlaybackUrl.js +19 -0
- package/lib/esm/workouts/list/cards/WorkoutCard.js +1 -1
- package/lib/esm/workouts/list/loaders/db.js +1 -1
- package/lib/types/autoupdate/api.d.ts +2 -1
- package/lib/types/ride/route/RLVDisplayService.d.ts +1 -0
- package/lib/types/video/Mp4BoxParser.d.ts +10 -0
- package/lib/types/video/index.d.ts +2 -0
- package/lib/types/video/resolvePlaybackUrl.d.ts +1 -0
- package/package.json +8 -6
|
@@ -60,14 +60,14 @@ let IncyclistFitConvertApi = (() => {
|
|
|
60
60
|
url = response.data;
|
|
61
61
|
}
|
|
62
62
|
catch (error) {
|
|
63
|
-
throw new Error(`convert failed: (phase convert), reason: ${error.message}
|
|
63
|
+
throw new Error(`convert failed: (phase convert), reason: ${error.message}`, { cause: error });
|
|
64
64
|
}
|
|
65
65
|
try {
|
|
66
66
|
const response = await this.getApi().get(url, { responseType: 'arraybuffer' });
|
|
67
67
|
return response.data;
|
|
68
68
|
}
|
|
69
69
|
catch (error) {
|
|
70
|
-
throw new Error(`convert failed: (phase download), reason: ${error.message}
|
|
70
|
+
throw new Error(`convert failed: (phase download), reason: ${error.message}`, { cause: error });
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
getApi() {
|
|
@@ -265,8 +265,8 @@ let ActivitiesRepository = (() => {
|
|
|
265
265
|
if (typeof endPos !== 'number' && endPos?.value !== undefined) {
|
|
266
266
|
endPos = this.getUnitConverter().convert(endPos.value, 'distance', { from: endPos.unit, to: 'm' });
|
|
267
267
|
}
|
|
268
|
-
if (result?.length > 0 &&
|
|
269
|
-
result = result.filter(ai => ai.summary.endPos ===
|
|
268
|
+
if (result?.length > 0 && endPos !== undefined) {
|
|
269
|
+
result = result.filter(ai => ai.summary.endPos === endPos);
|
|
270
270
|
}
|
|
271
271
|
return result;
|
|
272
272
|
}
|
|
@@ -42,6 +42,7 @@ const service_1 = require("../base/service");
|
|
|
42
42
|
const decorators_1 = require("../base/decorators");
|
|
43
43
|
const UPDATE_SERVER_URL = 'UPDATE_SERVER_URL';
|
|
44
44
|
const DEFAULT_DOMAIN = 'incyclist.com';
|
|
45
|
+
const UPDATE_CHECK_TIMEOUT_MS = 5000;
|
|
45
46
|
let IncyclistUpdatesApi = (() => {
|
|
46
47
|
let _classDecorators = [types_1.Singleton];
|
|
47
48
|
let _classDescriptor;
|
|
@@ -96,10 +97,19 @@ let IncyclistUpdatesApi = (() => {
|
|
|
96
97
|
const base = this.getSetting(UPDATE_SERVER_URL, defaultUrl);
|
|
97
98
|
return base;
|
|
98
99
|
}
|
|
99
|
-
async _get(url,
|
|
100
|
+
async _get(url, config = {}) {
|
|
100
101
|
const api = this.getApi();
|
|
101
102
|
const baseUrl = this.getBaseUrl();
|
|
102
|
-
return await api.get(baseUrl + url, ...
|
|
103
|
+
return await api.get(baseUrl + url, { timeout: UPDATE_CHECK_TIMEOUT_MS, ...config });
|
|
104
|
+
}
|
|
105
|
+
isExpectedError(err) {
|
|
106
|
+
if (!err)
|
|
107
|
+
return false;
|
|
108
|
+
if (err.message === 'Network Error')
|
|
109
|
+
return true;
|
|
110
|
+
if (/timeout/i.test(err.message ?? ''))
|
|
111
|
+
return true;
|
|
112
|
+
return false;
|
|
103
113
|
}
|
|
104
114
|
async getLatestMacAppVersion() {
|
|
105
115
|
try {
|
|
@@ -114,7 +124,7 @@ let IncyclistUpdatesApi = (() => {
|
|
|
114
124
|
return { version, path, url, downloadUrl };
|
|
115
125
|
}
|
|
116
126
|
catch (err) {
|
|
117
|
-
if (err
|
|
127
|
+
if (!this.isExpectedError(err)) {
|
|
118
128
|
this.logError(err, 'getLatestMacAppVersion');
|
|
119
129
|
}
|
|
120
130
|
}
|
|
@@ -142,7 +152,7 @@ let IncyclistUpdatesApi = (() => {
|
|
|
142
152
|
return { version, path, url, downloadUrl };
|
|
143
153
|
}
|
|
144
154
|
catch (err) {
|
|
145
|
-
if (err
|
|
155
|
+
if (!this.isExpectedError(err)) {
|
|
146
156
|
this.logError(err, 'getLatestLinuxAppVersion');
|
|
147
157
|
}
|
|
148
158
|
}
|
|
@@ -165,7 +175,7 @@ let IncyclistUpdatesApi = (() => {
|
|
|
165
175
|
return { version, path, url, downloadUrl };
|
|
166
176
|
}
|
|
167
177
|
catch (err) {
|
|
168
|
-
if (err
|
|
178
|
+
if (!this.isExpectedError(err)) {
|
|
169
179
|
this.logError(err, 'getLatestWindowsAppVersion');
|
|
170
180
|
}
|
|
171
181
|
}
|
|
@@ -257,9 +257,8 @@ let CoachesService = (() => {
|
|
|
257
257
|
if (!route)
|
|
258
258
|
return;
|
|
259
259
|
const prevPosition = coach.getPosition() || route.points[0];
|
|
260
|
-
let nextPosition = undefined;
|
|
261
260
|
if (distance > 0) {
|
|
262
|
-
nextPosition = (0, route_1.getNextPosition)(route, { distance, prev: prevPosition });
|
|
261
|
+
const nextPosition = (0, route_1.getNextPosition)(route, { distance, prev: prevPosition });
|
|
263
262
|
if (nextPosition) {
|
|
264
263
|
const { slope } = nextPosition;
|
|
265
264
|
coach.sendDeviceUpdate({ slope: slope * realityFactor / 100 });
|
|
@@ -1134,7 +1134,7 @@ let DeviceRideService = (() => {
|
|
|
1134
1134
|
const adapters = this.getRideAdapters();
|
|
1135
1135
|
const config = this.getDeviceConfiguration();
|
|
1136
1136
|
const selectedDevices = selected ?? config.getSelectedDevices();
|
|
1137
|
-
let enabledCapabilities
|
|
1137
|
+
let enabledCapabilities;
|
|
1138
1138
|
const duplicates = this.checkAntSameDeviceID(adapters);
|
|
1139
1139
|
const toBeReplaced = duplicates.filter(dai => dai.udid === adapterInfo.udid).map(dai => dai.info.udid);
|
|
1140
1140
|
if (this.simulatorEnforced) {
|
|
@@ -146,7 +146,6 @@ class MapArea {
|
|
|
146
146
|
};
|
|
147
147
|
const addOption = (newPath, res, reverse) => {
|
|
148
148
|
const optWay = way;
|
|
149
|
-
let path = [];
|
|
150
149
|
if (newPath.length === 1) {
|
|
151
150
|
addWaysCrossing(res);
|
|
152
151
|
}
|
|
@@ -156,7 +155,7 @@ class MapArea {
|
|
|
156
155
|
newPath[0].id === way.path.at(-1).id)) {
|
|
157
156
|
addWaysCrossing(res);
|
|
158
157
|
}
|
|
159
|
-
path = newPath;
|
|
158
|
+
const path = newPath;
|
|
160
159
|
if (reverse)
|
|
161
160
|
path.reverse();
|
|
162
161
|
res.push({ wayId: optWay.id, path });
|
|
@@ -172,6 +172,7 @@ let MapAreaService = (() => {
|
|
|
172
172
|
let ts;
|
|
173
173
|
for (let i = 0; i < 2; i++) {
|
|
174
174
|
try {
|
|
175
|
+
ts = Date.now();
|
|
175
176
|
const bounds = (0, utils_1.getBounds)(location?.lat, location?.lng, this.radius);
|
|
176
177
|
const query = (0, utils_1.buildQuery)(consts_1.GET_WAYS_IN_AREA, bounds);
|
|
177
178
|
const openmapData = await this.getOverpassAPI().query(query, DEFAULT_TIMEOUT);
|
|
@@ -873,7 +873,7 @@ let RideDisplayService = (() => {
|
|
|
873
873
|
}
|
|
874
874
|
simulatorPowerUp(mode, powerInc) {
|
|
875
875
|
const simMode = mode.getSetting('mode');
|
|
876
|
-
let request
|
|
876
|
+
let request;
|
|
877
877
|
if (simMode === 'Power') {
|
|
878
878
|
const currentPowerTarget = mode.getSetting('power');
|
|
879
879
|
const targetPower = Number(currentPowerTarget) + powerInc;
|
|
@@ -202,7 +202,6 @@ let FreeRideDisplayService = (() => {
|
|
|
202
202
|
findPointBeforeOrAtTurn(path) {
|
|
203
203
|
let prevPointIdx;
|
|
204
204
|
let atPoint = false;
|
|
205
|
-
let found = false;
|
|
206
205
|
let idx = path.findIndex(p => p.routeDistance >= this.position.routeDistance);
|
|
207
206
|
if (idx > 0) {
|
|
208
207
|
const point = path[idx];
|
|
@@ -212,7 +211,7 @@ let FreeRideDisplayService = (() => {
|
|
|
212
211
|
}
|
|
213
212
|
prevPointIdx = idx;
|
|
214
213
|
}
|
|
215
|
-
found = idx !== -1;
|
|
214
|
+
const found = idx !== -1;
|
|
216
215
|
return { prevPointIdx, atPoint, found };
|
|
217
216
|
}
|
|
218
217
|
getDisplayProperties(props) {
|
|
@@ -403,6 +403,11 @@ let RLVDisplayService = (() => {
|
|
|
403
403
|
}
|
|
404
404
|
this.emit('state-update');
|
|
405
405
|
}
|
|
406
|
+
onConvertError(message, video = this.currentVideo) {
|
|
407
|
+
if (!video)
|
|
408
|
+
return;
|
|
409
|
+
this.onVideoLoadError({ message }, video);
|
|
410
|
+
}
|
|
406
411
|
onVideoStalled(time, bufferedTime, buffers, video = this.currentVideo) {
|
|
407
412
|
video.syncHelper.onVideoStalled(time, bufferedTime);
|
|
408
413
|
}
|
|
@@ -509,25 +514,10 @@ let RLVDisplayService = (() => {
|
|
|
509
514
|
video.playback = 'converted';
|
|
510
515
|
video.source = new video_1.VideoConversion(this.cleanupUrl(src), video?.route);
|
|
511
516
|
video.source.getObserver().on('convert-progress', this.onConvertUpdate.bind(this));
|
|
517
|
+
video.source.getObserver().on('convert-error', (message) => this.onConvertError(message, video));
|
|
512
518
|
}
|
|
513
519
|
cleanupUrl(url) {
|
|
514
|
-
|
|
515
|
-
return;
|
|
516
|
-
let fileName = url;
|
|
517
|
-
const lc = url.toLowerCase();
|
|
518
|
-
if (fileName.startsWith('incyclist:'))
|
|
519
|
-
fileName = fileName.replace('incyclist:', 'file:');
|
|
520
|
-
if (fileName.startsWith('video:') && !lc.endsWith('.avi'))
|
|
521
|
-
return fileName.replace('video:', 'file:');
|
|
522
|
-
if (fileName.startsWith('file:') && !lc.endsWith('.avi') && !this.isMobile())
|
|
523
|
-
return fileName.replace('file:', 'video:');
|
|
524
|
-
if (fileName.startsWith('video:') && lc.endsWith('.avi'))
|
|
525
|
-
return fileName;
|
|
526
|
-
if (fileName.startsWith('file:') || fileName.startsWith('http:') || fileName.startsWith('https:') || fileName.startsWith('/'))
|
|
527
|
-
return fileName;
|
|
528
|
-
if (fileName.startsWith('content:'))
|
|
529
|
-
return fileName;
|
|
530
|
-
return `./${fileName}`;
|
|
520
|
+
return (0, video_1.resolvePlaybackUrl)(url, this.isMobile());
|
|
531
521
|
}
|
|
532
522
|
isLoopEnabled() {
|
|
533
523
|
return this.isLoop() && !this.startSettings?.loopOverwrite;
|
|
@@ -244,7 +244,7 @@ class XMLParser {
|
|
|
244
244
|
}
|
|
245
245
|
catch (err) {
|
|
246
246
|
this.getLogger().logEvent({ message: 'xml details', error: err.message, mappings });
|
|
247
|
-
throw new Error('Could not parse XML File');
|
|
247
|
+
throw new Error('Could not parse XML File', { cause: err });
|
|
248
248
|
}
|
|
249
249
|
}
|
|
250
250
|
}
|
|
@@ -57,7 +57,7 @@ const detectAnomalies = (points, maxCount) => {
|
|
|
57
57
|
};
|
|
58
58
|
const onSpeedJumpDetected = (point, idx) => {
|
|
59
59
|
const prevPoint = data[lastValidSpeedIdx];
|
|
60
|
-
let
|
|
60
|
+
let nextNullIdx;
|
|
61
61
|
if (prevPoint.speed === 0) {
|
|
62
62
|
anomalies.push({ idx: lastValidSpeedIdx, type: 'noDistance', point });
|
|
63
63
|
}
|
|
@@ -72,7 +72,7 @@ const detectAnomalies = (points, maxCount) => {
|
|
|
72
72
|
continue;
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
|
-
anomalies.push({ idx, type: 'speedJump', point, nextNullIdx
|
|
75
|
+
anomalies.push({ idx, type: 'speedJump', point, nextNullIdx });
|
|
76
76
|
}
|
|
77
77
|
};
|
|
78
78
|
data.forEach((point, idx) => {
|
|
@@ -106,7 +106,7 @@ const fixAnomaly = (anomaly, points) => {
|
|
|
106
106
|
exports.fixAnomaly = fixAnomaly;
|
|
107
107
|
const fixAnomalies = (points, maxAttempts = 1000) => {
|
|
108
108
|
let fixed = 0;
|
|
109
|
-
let anomalies
|
|
109
|
+
let anomalies;
|
|
110
110
|
const tsStart = Date.now();
|
|
111
111
|
let prev;
|
|
112
112
|
let stuck = false;
|
|
@@ -159,11 +159,12 @@ let RouteCard = (() => {
|
|
|
159
159
|
const videoUrl = descr.videoUrl;
|
|
160
160
|
if (!descr.videoUrl)
|
|
161
161
|
return true;
|
|
162
|
+
const normalizedUrl = videoUrl.replace('video:////', 'video:///').replace('file:////', 'file:///');
|
|
162
163
|
let path = undefined;
|
|
163
|
-
if (
|
|
164
|
-
path =
|
|
165
|
-
if (
|
|
166
|
-
path =
|
|
164
|
+
if (normalizedUrl.startsWith('file:///'))
|
|
165
|
+
path = normalizedUrl.replace('file://', '');
|
|
166
|
+
if (normalizedUrl.startsWith('video:///'))
|
|
167
|
+
path = normalizedUrl.replace('video://', '');
|
|
167
168
|
if (path) {
|
|
168
169
|
return await this.fileExists(path);
|
|
169
170
|
}
|
|
@@ -83,7 +83,7 @@ let RoutesDbLoader = (() => {
|
|
|
83
83
|
} };
|
|
84
84
|
const updatedDescr = this.buildRouteDBInfo(route.description);
|
|
85
85
|
const idx = this.routeDescriptions.findIndex(d => d.id === route.description.id);
|
|
86
|
-
let changed
|
|
86
|
+
let changed;
|
|
87
87
|
if (idx === -1) {
|
|
88
88
|
this.routeDescriptions.push((0, clone_1.default)(updatedDescr));
|
|
89
89
|
this.routeHashes[route.description.id] = stringify(updatedDescr);
|
package/lib/cjs/ui/service.js
CHANGED
|
@@ -50,7 +50,9 @@ const monitoring_1 = require("../monitoring");
|
|
|
50
50
|
const appstate_1 = require("../appstate");
|
|
51
51
|
const pages_1 = require("../base/pages");
|
|
52
52
|
const utils_1 = require("../utils");
|
|
53
|
+
const sleep_1 = require("../utils/sleep");
|
|
53
54
|
const BACKGROUND_PAUSE_TIMEOUT_MS = 300000;
|
|
55
|
+
const APP_EXIT_TIMEOUT_MS = 5000;
|
|
54
56
|
let UserInterfaceServcie = (() => {
|
|
55
57
|
let _classDecorators = [decorators_1.Singleton];
|
|
56
58
|
let _classDescriptor;
|
|
@@ -146,9 +148,12 @@ let UserInterfaceServcie = (() => {
|
|
|
146
148
|
this.stopHeartbeatWorker();
|
|
147
149
|
this.sendAppExitMessage();
|
|
148
150
|
pages_1.IncyclistPageService.closePage();
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
151
|
+
const cleanup = async () => {
|
|
152
|
+
await (0, utils_1.waitNextTick)();
|
|
153
|
+
await (0, devices_1.useDevicePairing)().exit();
|
|
154
|
+
await (0, devices_1.useDeviceAccess)().terminate();
|
|
155
|
+
};
|
|
156
|
+
await Promise.race([cleanup(), (0, sleep_1.sleep)(APP_EXIT_TIMEOUT_MS)]);
|
|
152
157
|
this.appState = 'Stopped';
|
|
153
158
|
this.logEvent({ message: 'onAppExit finished' });
|
|
154
159
|
this.isTerminated = true;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseMp4Boxes = void 0;
|
|
4
|
+
const readBoxHeader = (buf, offset) => {
|
|
5
|
+
if (offset + 8 > buf.length)
|
|
6
|
+
return undefined;
|
|
7
|
+
const size32 = buf.readUInt32BE(offset);
|
|
8
|
+
const type = buf.toString('ascii', offset + 4, offset + 8);
|
|
9
|
+
let headerSize = 8;
|
|
10
|
+
let size = size32;
|
|
11
|
+
if (size32 === 1) {
|
|
12
|
+
if (offset + 16 > buf.length)
|
|
13
|
+
return undefined;
|
|
14
|
+
const high = buf.readUInt32BE(offset + 8);
|
|
15
|
+
const low = buf.readUInt32BE(offset + 12);
|
|
16
|
+
size = high * 2 ** 32 + low;
|
|
17
|
+
headerSize = 16;
|
|
18
|
+
}
|
|
19
|
+
else if (size32 === 0) {
|
|
20
|
+
size = buf.length - offset;
|
|
21
|
+
}
|
|
22
|
+
return { type, start: offset, size, headerSize, bodyStart: offset + headerSize, bodyEnd: offset + size };
|
|
23
|
+
};
|
|
24
|
+
const findBoxes = (buf, start, end) => {
|
|
25
|
+
const boxes = [];
|
|
26
|
+
let offset = start;
|
|
27
|
+
while (offset < end) {
|
|
28
|
+
const box = readBoxHeader(buf, offset);
|
|
29
|
+
if (!box || box.size <= 0 || box.bodyEnd > end)
|
|
30
|
+
break;
|
|
31
|
+
boxes.push(box);
|
|
32
|
+
offset = box.bodyEnd;
|
|
33
|
+
}
|
|
34
|
+
return boxes;
|
|
35
|
+
};
|
|
36
|
+
const findBox = (boxes, type) => boxes.find(b => b.type === type);
|
|
37
|
+
const findMoovBySignature = (buf) => {
|
|
38
|
+
const signature = Buffer.from('moov', 'ascii');
|
|
39
|
+
let searchFrom = 0;
|
|
40
|
+
while (searchFrom < buf.length) {
|
|
41
|
+
const typeOffset = buf.indexOf(signature, searchFrom);
|
|
42
|
+
if (typeOffset === -1 || typeOffset < 4)
|
|
43
|
+
return undefined;
|
|
44
|
+
const box = readBoxHeader(buf, typeOffset - 4);
|
|
45
|
+
if (box?.type === 'moov' && box.bodyEnd <= buf.length)
|
|
46
|
+
return box;
|
|
47
|
+
searchFrom = typeOffset + 4;
|
|
48
|
+
}
|
|
49
|
+
return undefined;
|
|
50
|
+
};
|
|
51
|
+
const parseFtyp = (buf, box) => {
|
|
52
|
+
if (box.bodyStart + 4 > buf.length)
|
|
53
|
+
return undefined;
|
|
54
|
+
return buf.toString('ascii', box.bodyStart, box.bodyStart + 4);
|
|
55
|
+
};
|
|
56
|
+
const parseHandlerType = (buf, hdlrBox) => {
|
|
57
|
+
const offset = hdlrBox.bodyStart + 8;
|
|
58
|
+
if (offset + 4 > buf.length)
|
|
59
|
+
return undefined;
|
|
60
|
+
return buf.toString('ascii', offset, offset + 4);
|
|
61
|
+
};
|
|
62
|
+
const parseStsdCodec = (buf, stsdBox) => {
|
|
63
|
+
const entryOffset = stsdBox.bodyStart + 8;
|
|
64
|
+
if (entryOffset + 8 > buf.length)
|
|
65
|
+
return undefined;
|
|
66
|
+
return buf.toString('ascii', entryOffset + 4, entryOffset + 8);
|
|
67
|
+
};
|
|
68
|
+
const parseTkhdDimensions = (buf, tkhdBox) => {
|
|
69
|
+
if (tkhdBox.bodyStart >= buf.length)
|
|
70
|
+
return {};
|
|
71
|
+
const version = buf.readUInt8(tkhdBox.bodyStart);
|
|
72
|
+
const fixedFieldsSize = version === 1 ? 96 : 76;
|
|
73
|
+
const offset = tkhdBox.bodyStart + fixedFieldsSize;
|
|
74
|
+
if (offset + 8 > buf.length)
|
|
75
|
+
return {};
|
|
76
|
+
const width = buf.readUInt32BE(offset) >> 16;
|
|
77
|
+
const height = buf.readUInt32BE(offset + 4) >> 16;
|
|
78
|
+
return { width, height };
|
|
79
|
+
};
|
|
80
|
+
const parseTrak = (buf, trakBox) => {
|
|
81
|
+
const trakChildren = findBoxes(buf, trakBox.bodyStart, trakBox.bodyEnd);
|
|
82
|
+
const tkhdBox = findBox(trakChildren, 'tkhd');
|
|
83
|
+
const mdiaBox = findBox(trakChildren, 'mdia');
|
|
84
|
+
if (!mdiaBox)
|
|
85
|
+
return {};
|
|
86
|
+
const mdiaChildren = findBoxes(buf, mdiaBox.bodyStart, mdiaBox.bodyEnd);
|
|
87
|
+
const hdlrBox = findBox(mdiaChildren, 'hdlr');
|
|
88
|
+
const minfBox = findBox(mdiaChildren, 'minf');
|
|
89
|
+
const handlerType = hdlrBox ? parseHandlerType(buf, hdlrBox) : undefined;
|
|
90
|
+
let codec;
|
|
91
|
+
if (minfBox) {
|
|
92
|
+
const minfChildren = findBoxes(buf, minfBox.bodyStart, minfBox.bodyEnd);
|
|
93
|
+
const stblBox = findBox(minfChildren, 'stbl');
|
|
94
|
+
if (stblBox) {
|
|
95
|
+
const stblChildren = findBoxes(buf, stblBox.bodyStart, stblBox.bodyEnd);
|
|
96
|
+
const stsdBox = findBox(stblChildren, 'stsd');
|
|
97
|
+
if (stsdBox)
|
|
98
|
+
codec = parseStsdCodec(buf, stsdBox);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const dimensions = (handlerType === 'vide' && tkhdBox) ? parseTkhdDimensions(buf, tkhdBox) : {};
|
|
102
|
+
return { handlerType, codec, ...dimensions };
|
|
103
|
+
};
|
|
104
|
+
const parseMp4Boxes = (headBuffer, tailBuffer) => {
|
|
105
|
+
const result = { moovLocation: 'not-found', incomplete: true };
|
|
106
|
+
if (headBuffer?.length > 0) {
|
|
107
|
+
const headBoxes = findBoxes(headBuffer, 0, headBuffer.length);
|
|
108
|
+
const ftypBox = findBox(headBoxes, 'ftyp');
|
|
109
|
+
if (ftypBox)
|
|
110
|
+
result.containerBrand = parseFtyp(headBuffer, ftypBox);
|
|
111
|
+
const moovBox = findBox(headBoxes, 'moov');
|
|
112
|
+
if (moovBox) {
|
|
113
|
+
applyMoov(headBuffer, moovBox, result);
|
|
114
|
+
result.moovLocation = 'head';
|
|
115
|
+
result.incomplete = false;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (result.moovLocation === 'not-found' && tailBuffer?.length > 0) {
|
|
119
|
+
const moovBox = findMoovBySignature(tailBuffer);
|
|
120
|
+
if (moovBox) {
|
|
121
|
+
applyMoov(tailBuffer, moovBox, result);
|
|
122
|
+
result.moovLocation = 'tail';
|
|
123
|
+
result.incomplete = false;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return result;
|
|
127
|
+
};
|
|
128
|
+
exports.parseMp4Boxes = parseMp4Boxes;
|
|
129
|
+
const applyMoov = (buf, moovBox, result) => {
|
|
130
|
+
const moovChildren = findBoxes(buf, moovBox.bodyStart, moovBox.bodyEnd);
|
|
131
|
+
const trakBoxes = moovChildren.filter(b => b.type === 'trak');
|
|
132
|
+
trakBoxes.forEach(trakBox => {
|
|
133
|
+
const { handlerType, codec, width, height } = parseTrak(buf, trakBox);
|
|
134
|
+
if (handlerType === 'vide') {
|
|
135
|
+
result.videoCodec = codec;
|
|
136
|
+
if (width)
|
|
137
|
+
result.width = width;
|
|
138
|
+
if (height)
|
|
139
|
+
result.height = height;
|
|
140
|
+
}
|
|
141
|
+
else if (handlerType === 'soun') {
|
|
142
|
+
result.audioCodec = codec;
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
};
|
|
@@ -115,7 +115,7 @@ let VideoConversion = (() => {
|
|
|
115
115
|
return undefined;
|
|
116
116
|
const clean = str.trim();
|
|
117
117
|
const parts = clean.split(':');
|
|
118
|
-
let seconds
|
|
118
|
+
let seconds;
|
|
119
119
|
if (parts.length === 3) {
|
|
120
120
|
const [h, m, s] = parts;
|
|
121
121
|
seconds = Number(h) * 3600 + Number(m) * 60 + Number.parseFloat(s);
|
package/lib/cjs/video/index.js
CHANGED
|
@@ -17,3 +17,5 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
17
17
|
__exportStar(require("./types"), exports);
|
|
18
18
|
__exportStar(require("./VideoSyncHelper"), exports);
|
|
19
19
|
__exportStar(require("./VideoConversion"), exports);
|
|
20
|
+
__exportStar(require("./Mp4BoxParser"), exports);
|
|
21
|
+
__exportStar(require("./resolvePlaybackUrl"), exports);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolvePlaybackUrl = void 0;
|
|
4
|
+
const resolvePlaybackUrl = (url, isMobile = false) => {
|
|
5
|
+
if (!url)
|
|
6
|
+
return undefined;
|
|
7
|
+
let fileName = url;
|
|
8
|
+
const lc = url.toLowerCase();
|
|
9
|
+
if (fileName.startsWith('incyclist:'))
|
|
10
|
+
fileName = fileName.replace('incyclist:', 'file:');
|
|
11
|
+
if (fileName.startsWith('video:') && !lc.endsWith('.avi'))
|
|
12
|
+
return fileName.replace('video:', 'file:');
|
|
13
|
+
if (fileName.startsWith('file:') && !lc.endsWith('.avi') && !isMobile)
|
|
14
|
+
return fileName.replace('file:', 'video:');
|
|
15
|
+
if (fileName.startsWith('video:') && lc.endsWith('.avi'))
|
|
16
|
+
return fileName;
|
|
17
|
+
if (fileName.startsWith('file:') || fileName.startsWith('http:') || fileName.startsWith('https:') || fileName.startsWith('/'))
|
|
18
|
+
return fileName;
|
|
19
|
+
if (fileName.startsWith('content:'))
|
|
20
|
+
return fileName;
|
|
21
|
+
return `./${fileName}`;
|
|
22
|
+
};
|
|
23
|
+
exports.resolvePlaybackUrl = resolvePlaybackUrl;
|
|
@@ -57,14 +57,14 @@ let IncyclistFitConvertApi = (() => {
|
|
|
57
57
|
url = response.data;
|
|
58
58
|
}
|
|
59
59
|
catch (error) {
|
|
60
|
-
throw new Error(`convert failed: (phase convert), reason: ${error.message}
|
|
60
|
+
throw new Error(`convert failed: (phase convert), reason: ${error.message}`, { cause: error });
|
|
61
61
|
}
|
|
62
62
|
try {
|
|
63
63
|
const response = await this.getApi().get(url, { responseType: 'arraybuffer' });
|
|
64
64
|
return response.data;
|
|
65
65
|
}
|
|
66
66
|
catch (error) {
|
|
67
|
-
throw new Error(`convert failed: (phase download), reason: ${error.message}
|
|
67
|
+
throw new Error(`convert failed: (phase download), reason: ${error.message}`, { cause: error });
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
getApi() {
|
|
@@ -262,8 +262,8 @@ let ActivitiesRepository = (() => {
|
|
|
262
262
|
if (typeof endPos !== 'number' && endPos?.value !== undefined) {
|
|
263
263
|
endPos = this.getUnitConverter().convert(endPos.value, 'distance', { from: endPos.unit, to: 'm' });
|
|
264
264
|
}
|
|
265
|
-
if (result?.length > 0 &&
|
|
266
|
-
result = result.filter(ai => ai.summary.endPos ===
|
|
265
|
+
if (result?.length > 0 && endPos !== undefined) {
|
|
266
|
+
result = result.filter(ai => ai.summary.endPos === endPos);
|
|
267
267
|
}
|
|
268
268
|
return result;
|
|
269
269
|
}
|