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.
Files changed (62) hide show
  1. package/lib/cjs/activities/base/api/fitconvert/fitconvert.js +2 -2
  2. package/lib/cjs/activities/base/repo/db.js +2 -2
  3. package/lib/cjs/activities/base/utils/activity.js +1 -0
  4. package/lib/cjs/apps/base/api/velohero/api.js +1 -1
  5. package/lib/cjs/apps/intervals/IntervalsAppConnection.js +1 -1
  6. package/lib/cjs/apps/komoot/KomootAppConnection.js +1 -1
  7. package/lib/cjs/apps/strava/StravaAppConnection.js +1 -1
  8. package/lib/cjs/apps/velohero/VeloHeroAppConnection.js +1 -1
  9. package/lib/cjs/autoupdate/api.js +15 -5
  10. package/lib/cjs/coaches/service.js +1 -2
  11. package/lib/cjs/devices/ride/service.js +1 -1
  12. package/lib/cjs/maps/MapArea/MapArea.js +1 -2
  13. package/lib/cjs/maps/MapArea/service.js +1 -0
  14. package/lib/cjs/ride/display/service.js +1 -1
  15. package/lib/cjs/ride/route/FreeRideDisplayService.js +1 -2
  16. package/lib/cjs/ride/route/RLVDisplayService.js +7 -17
  17. package/lib/cjs/routes/base/parsers/incyclist.js +1 -1
  18. package/lib/cjs/routes/base/parsers/xml.js +1 -1
  19. package/lib/cjs/routes/base/utils/points.js +3 -3
  20. package/lib/cjs/routes/list/cards/RouteCard.js +5 -4
  21. package/lib/cjs/routes/list/loaders/db.js +1 -1
  22. package/lib/cjs/ui/service.js +8 -3
  23. package/lib/cjs/video/Mp4BoxParser.js +145 -0
  24. package/lib/cjs/video/VideoConversion.js +1 -1
  25. package/lib/cjs/video/index.js +2 -0
  26. package/lib/cjs/video/resolvePlaybackUrl.js +23 -0
  27. package/lib/cjs/workouts/list/cards/WorkoutCard.js +1 -1
  28. package/lib/cjs/workouts/list/loaders/db.js +1 -1
  29. package/lib/esm/activities/base/api/fitconvert/fitconvert.js +2 -2
  30. package/lib/esm/activities/base/repo/db.js +2 -2
  31. package/lib/esm/activities/base/utils/activity.js +1 -0
  32. package/lib/esm/apps/base/api/velohero/api.js +1 -1
  33. package/lib/esm/apps/intervals/IntervalsAppConnection.js +1 -1
  34. package/lib/esm/apps/komoot/KomootAppConnection.js +1 -1
  35. package/lib/esm/apps/strava/StravaAppConnection.js +1 -1
  36. package/lib/esm/apps/velohero/VeloHeroAppConnection.js +1 -1
  37. package/lib/esm/autoupdate/api.js +15 -5
  38. package/lib/esm/coaches/service.js +1 -2
  39. package/lib/esm/devices/ride/service.js +1 -1
  40. package/lib/esm/maps/MapArea/MapArea.js +1 -2
  41. package/lib/esm/maps/MapArea/service.js +1 -0
  42. package/lib/esm/ride/display/service.js +1 -1
  43. package/lib/esm/ride/route/FreeRideDisplayService.js +1 -2
  44. package/lib/esm/ride/route/RLVDisplayService.js +8 -18
  45. package/lib/esm/routes/base/parsers/incyclist.js +1 -1
  46. package/lib/esm/routes/base/parsers/xml.js +1 -1
  47. package/lib/esm/routes/base/utils/points.js +3 -3
  48. package/lib/esm/routes/list/cards/RouteCard.js +5 -4
  49. package/lib/esm/routes/list/loaders/db.js +1 -1
  50. package/lib/esm/ui/service.js +8 -3
  51. package/lib/esm/video/Mp4BoxParser.js +141 -0
  52. package/lib/esm/video/VideoConversion.js +1 -1
  53. package/lib/esm/video/index.js +2 -0
  54. package/lib/esm/video/resolvePlaybackUrl.js +19 -0
  55. package/lib/esm/workouts/list/cards/WorkoutCard.js +1 -1
  56. package/lib/esm/workouts/list/loaders/db.js +1 -1
  57. package/lib/types/autoupdate/api.d.ts +2 -1
  58. package/lib/types/ride/route/RLVDisplayService.d.ts +1 -0
  59. package/lib/types/video/Mp4BoxParser.d.ts +10 -0
  60. package/lib/types/video/index.d.ts +2 -0
  61. package/lib/types/video/resolvePlaybackUrl.d.ts +1 -0
  62. 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 && criteria?.endPos !== undefined) {
269
- result = result.filter(ai => ai.summary.endPos === criteria.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
  }
@@ -236,6 +236,7 @@ let Activity = (() => {
236
236
  }
237
237
  catch (err) {
238
238
  this.logError(err, 'upload');
239
+ error = err.message;
239
240
  }
240
241
  }
241
242
  this.markUploading(connectedApp, false);
@@ -38,7 +38,7 @@ class VeloHeroApi extends base_1.AppApiBase {
38
38
  const { response } = err;
39
39
  error = `HTTP error ${response.status}: ${response.statusText}`;
40
40
  }
41
- throw new Error(error);
41
+ throw new Error(error, { cause: err });
42
42
  }
43
43
  }
44
44
  getAccountType() {
@@ -122,7 +122,7 @@ let IntervalsAppConnection = (() => {
122
122
  return this.api;
123
123
  }
124
124
  initAuth() {
125
- let isInitialized = false;
125
+ let isInitialized;
126
126
  try {
127
127
  const auth = this.getCredentials();
128
128
  if (auth) {
@@ -119,7 +119,7 @@ let KomootAppConnection = (() => {
119
119
  return false;
120
120
  }
121
121
  checkAuth() {
122
- let isInitialized = false;
122
+ let isInitialized;
123
123
  let credentials;
124
124
  if (!this.getUserSettings().isInitialized) {
125
125
  return false;
@@ -120,7 +120,7 @@ let StravaAppConnection = (() => {
120
120
  return this.api;
121
121
  }
122
122
  initAuth() {
123
- let isInitialized = false;
123
+ let isInitialized;
124
124
  try {
125
125
  const auth = this.getCredentials();
126
126
  const clientId = this.getSecret('STRAVA_CLIENT_ID');
@@ -105,7 +105,7 @@ let VeloHeroAppConnection = (() => {
105
105
  return this.api;
106
106
  }
107
107
  initAuth() {
108
- let isInitialized = false;
108
+ let isInitialized;
109
109
  if (!this.getUserSettings().isInitialized) {
110
110
  return false;
111
111
  }
@@ -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, ...args) {
100
+ async _get(url, config = {}) {
100
101
  const api = this.getApi();
101
102
  const baseUrl = this.getBaseUrl();
102
- return await api.get(baseUrl + url, ...args);
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.message !== 'Network Error') {
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.message !== 'Network Error') {
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.message !== 'Network Error') {
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
- if (!url)
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;
@@ -89,7 +89,7 @@ class IncyclistXMLParser extends xml_1.XMLParser {
89
89
  }
90
90
  catch (err) {
91
91
  if (!data['gpx-file-path'])
92
- throw new Error('<gpx-file-path> missing in XML');
92
+ throw new Error('<gpx-file-path> missing in XML', { cause: err });
93
93
  throw err;
94
94
  }
95
95
  }
@@ -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 nextJumpIdx, nextNullIdx;
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, nextJumpIdx });
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 (videoUrl.startsWith('file:///'))
164
- path = videoUrl.replace('file:///', '');
165
- if (videoUrl.startsWith('video:///'))
166
- path = videoUrl.replace('video:///', '');
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 = false;
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);
@@ -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
- await (0, utils_1.waitNextTick)();
150
- await (0, devices_1.useDevicePairing)().exit();
151
- await (0, devices_1.useDeviceAccess)().terminate();
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 = 0;
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);
@@ -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;
@@ -203,7 +203,7 @@ let WorkoutCard = (() => {
203
203
  }
204
204
  async _delete() {
205
205
  await (0, utils_1.waitNextTick)();
206
- let deleted = false;
206
+ let deleted;
207
207
  try {
208
208
  this.deleteObserver.emit('started');
209
209
  await this.getRepo().delete(this.workout);
@@ -97,7 +97,7 @@ let WorkoutsDbLoader = (() => {
97
97
  };
98
98
  let prev;
99
99
  const idx = this.workouts.findIndex(d => d.id === workout.id);
100
- let changed = false;
100
+ let changed;
101
101
  if (idx === -1) {
102
102
  changed = true;
103
103
  this.workouts.push(workout);
@@ -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 && criteria?.endPos !== undefined) {
266
- result = result.filter(ai => ai.summary.endPos === criteria.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
  }
@@ -233,6 +233,7 @@ let Activity = (() => {
233
233
  }
234
234
  catch (err) {
235
235
  this.logError(err, 'upload');
236
+ error = err.message;
236
237
  }
237
238
  }
238
239
  this.markUploading(connectedApp, false);
@@ -35,7 +35,7 @@ export class VeloHeroApi extends AppApiBase {
35
35
  const { response } = err;
36
36
  error = `HTTP error ${response.status}: ${response.statusText}`;
37
37
  }
38
- throw new Error(error);
38
+ throw new Error(error, { cause: err });
39
39
  }
40
40
  }
41
41
  getAccountType() {
@@ -119,7 +119,7 @@ let IntervalsAppConnection = (() => {
119
119
  return this.api;
120
120
  }
121
121
  initAuth() {
122
- let isInitialized = false;
122
+ let isInitialized;
123
123
  try {
124
124
  const auth = this.getCredentials();
125
125
  if (auth) {
@@ -116,7 +116,7 @@ let KomootAppConnection = (() => {
116
116
  return false;
117
117
  }
118
118
  checkAuth() {
119
- let isInitialized = false;
119
+ let isInitialized;
120
120
  let credentials;
121
121
  if (!this.getUserSettings().isInitialized) {
122
122
  return false;