rx-player 4.5.0-dev.2026060400 → 4.5.0
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/CHANGELOG.md +15 -14
- package/VERSION +1 -1
- package/dist/commonjs/__GENERATED_CODE/embedded_dash_wasm.d.ts.map +1 -1
- package/dist/commonjs/__GENERATED_CODE/embedded_dash_wasm.js +1 -1
- package/dist/commonjs/__GENERATED_CODE/embedded_worker.d.ts.map +1 -1
- package/dist/commonjs/__GENERATED_CODE/embedded_worker.js +1 -1
- package/dist/commonjs/core/fetchers/utils/schedule_request.d.ts.map +1 -1
- package/dist/commonjs/core/fetchers/utils/schedule_request.js +5 -2
- package/dist/commonjs/main_thread/api/public_api.js +2 -2
- package/dist/commonjs/main_thread/core_interface/monothread.d.ts.map +1 -1
- package/dist/commonjs/main_thread/core_interface/monothread.js +3 -2
- package/dist/commonjs/main_thread/render_thumbnail.d.ts.map +1 -1
- package/dist/commonjs/main_thread/render_thumbnail.js +7 -4
- package/dist/commonjs/main_thread/tracks_store/media_element_tracks_store.js +28 -27
- package/dist/es2017/__GENERATED_CODE/embedded_dash_wasm.d.ts.map +1 -1
- package/dist/es2017/__GENERATED_CODE/embedded_dash_wasm.js +1 -1
- package/dist/es2017/__GENERATED_CODE/embedded_worker.d.ts.map +1 -1
- package/dist/es2017/__GENERATED_CODE/embedded_worker.js +1 -1
- package/dist/es2017/core/fetchers/utils/schedule_request.d.ts.map +1 -1
- package/dist/es2017/core/fetchers/utils/schedule_request.js +5 -2
- package/dist/es2017/main_thread/api/public_api.js +2 -2
- package/dist/es2017/main_thread/core_interface/monothread.d.ts.map +1 -1
- package/dist/es2017/main_thread/core_interface/monothread.js +3 -2
- package/dist/es2017/main_thread/render_thumbnail.d.ts.map +1 -1
- package/dist/es2017/main_thread/render_thumbnail.js +7 -4
- package/dist/es2017/main_thread/tracks_store/media_element_tracks_store.js +28 -27
- package/dist/mpd-parser.wasm +0 -0
- package/dist/worker.js +1 -1
- package/package.json +2 -2
- package/src/__GENERATED_CODE/embedded_dash_wasm.ts +1 -1
- package/src/__GENERATED_CODE/embedded_worker.ts +1 -1
- package/src/core/fetchers/utils/schedule_request.ts +4 -2
- package/src/main_thread/__tests__/render_thumbnail.test.ts +193 -0
- package/src/main_thread/api/public_api.ts +2 -2
- package/src/main_thread/core_interface/monothread.ts +3 -2
- package/src/main_thread/render_thumbnail.ts +7 -4
- package/src/main_thread/tracks_store/media_element_tracks_store.ts +18 -18
- package/src/parsers/manifest/dash/wasm-parser/rs/lib.rs +1 -0
|
@@ -46,7 +46,8 @@ function shouldRetry(error: unknown): boolean {
|
|
|
46
46
|
error.status === 415 || // some CDN seems to use that code when
|
|
47
47
|
// requesting low-latency segments too much
|
|
48
48
|
// in advance
|
|
49
|
-
error.status === 412
|
|
49
|
+
error.status === 412 ||
|
|
50
|
+
error.status === 429 // TODO: Handle optional Retry-After header
|
|
50
51
|
);
|
|
51
52
|
}
|
|
52
53
|
return (
|
|
@@ -64,7 +65,8 @@ function shouldRetry(error: unknown): boolean {
|
|
|
64
65
|
error.xhr.status === 415 || // some CDN seems to use that code when
|
|
65
66
|
// requesting low-latency segments too much
|
|
66
67
|
// in advance
|
|
67
|
-
error.xhr.status === 412
|
|
68
|
+
error.xhr.status === 412 ||
|
|
69
|
+
error.xhr.status === 429
|
|
68
70
|
);
|
|
69
71
|
}
|
|
70
72
|
return false;
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import TaskCanceller from "../../utils/task_canceller";
|
|
3
|
+
import type { IPublicApiContentInfos } from "../api/public_api";
|
|
4
|
+
import renderThumbnail from "../render_thumbnail";
|
|
5
|
+
|
|
6
|
+
const { mockFormatError, mockGetPeriodForTime } = vi.hoisted(() => {
|
|
7
|
+
return {
|
|
8
|
+
mockFormatError: vi.fn((err: unknown) => err),
|
|
9
|
+
mockGetPeriodForTime: vi.fn(() => ({
|
|
10
|
+
id: "period-1",
|
|
11
|
+
thumbnailTracks: [{ id: "thumb-track-1" }],
|
|
12
|
+
})),
|
|
13
|
+
};
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
vi.mock("../../errors", () => ({
|
|
17
|
+
formatError: mockFormatError,
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
vi.mock("../../manifest", () => ({
|
|
21
|
+
getPeriodForTime: mockGetPeriodForTime,
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
describe("renderThumbnail", () => {
|
|
25
|
+
let imageInstances: FakeImage[];
|
|
26
|
+
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
imageInstances = [];
|
|
29
|
+
vi.stubGlobal(
|
|
30
|
+
"Image",
|
|
31
|
+
class FakeImage {
|
|
32
|
+
public onload: null | (() => void) = null;
|
|
33
|
+
public onerror: null | (() => void) = null;
|
|
34
|
+
|
|
35
|
+
set src(_value: string) {
|
|
36
|
+
imageInstances.push(this);
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
);
|
|
40
|
+
vi.stubGlobal("URL", {
|
|
41
|
+
createObjectURL: vi.fn(() => "blob:thumbnail"),
|
|
42
|
+
revokeObjectURL: vi.fn(),
|
|
43
|
+
});
|
|
44
|
+
vi.spyOn(HTMLCanvasElement.prototype, "getContext").mockReturnValue({
|
|
45
|
+
drawImage: vi.fn(),
|
|
46
|
+
} as unknown as CanvasRenderingContext2D);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
afterEach(() => {
|
|
50
|
+
vi.unstubAllGlobals();
|
|
51
|
+
vi.restoreAllMocks();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("keeps the newer thumbnail when an aborted older request finishes later", async () => {
|
|
55
|
+
let resolveFirstFetch!: (value: IThumbnailResponseLike) => void;
|
|
56
|
+
const contentInfos = createContentInfos(
|
|
57
|
+
vi
|
|
58
|
+
.fn()
|
|
59
|
+
.mockImplementationOnce(
|
|
60
|
+
() =>
|
|
61
|
+
new Promise<IThumbnailResponseLike>((resolve) => {
|
|
62
|
+
resolveFirstFetch = resolve;
|
|
63
|
+
}),
|
|
64
|
+
)
|
|
65
|
+
.mockResolvedValueOnce(makeThumbnailResponse(10, 20)),
|
|
66
|
+
);
|
|
67
|
+
const container = document.createElement("div");
|
|
68
|
+
|
|
69
|
+
const firstPromise = renderThumbnail(contentInfos, { container, time: 0.5 });
|
|
70
|
+
const secondPromise = renderThumbnail(contentInfos, { container, time: 12 });
|
|
71
|
+
|
|
72
|
+
await Promise.resolve();
|
|
73
|
+
expect(imageInstances).toHaveLength(1);
|
|
74
|
+
imageInstances[0].onload?.();
|
|
75
|
+
await expect(secondPromise).resolves.toBeUndefined();
|
|
76
|
+
expect(container.childElementCount).toBe(1);
|
|
77
|
+
|
|
78
|
+
resolveFirstFetch(makeThumbnailResponse(0, 10));
|
|
79
|
+
await expect(firstPromise).rejects.toMatchObject({ code: "ABORTED" });
|
|
80
|
+
expect(container.childElementCount).toBe(1);
|
|
81
|
+
expect(container.firstElementChild?.className).toBe("__rx-thumbnail__");
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("does not remove the newer pending request when an older request aborts", async () => {
|
|
85
|
+
let resolveFirstFetch!: (value: IThumbnailResponseLike) => void;
|
|
86
|
+
const contentInfos = createContentInfos(
|
|
87
|
+
vi
|
|
88
|
+
.fn()
|
|
89
|
+
.mockImplementationOnce(
|
|
90
|
+
() =>
|
|
91
|
+
new Promise<IThumbnailResponseLike>((resolve) => {
|
|
92
|
+
resolveFirstFetch = resolve;
|
|
93
|
+
}),
|
|
94
|
+
)
|
|
95
|
+
.mockResolvedValueOnce(makeThumbnailResponse(10, 20)),
|
|
96
|
+
);
|
|
97
|
+
const container = document.createElement("div");
|
|
98
|
+
|
|
99
|
+
const firstPromise = renderThumbnail(contentInfos, { container, time: 0.5 });
|
|
100
|
+
const secondPromise = renderThumbnail(contentInfos, { container, time: 12 });
|
|
101
|
+
const pendingRequest =
|
|
102
|
+
contentInfos.thumbnailRequestsInfo.pendingRequests.get(container);
|
|
103
|
+
|
|
104
|
+
expect(pendingRequest).toBeDefined();
|
|
105
|
+
await Promise.resolve();
|
|
106
|
+
expect(imageInstances).toHaveLength(1);
|
|
107
|
+
|
|
108
|
+
resolveFirstFetch(makeThumbnailResponse(0, 10));
|
|
109
|
+
await expect(firstPromise).rejects.toMatchObject({ code: "ABORTED" });
|
|
110
|
+
expect(contentInfos.thumbnailRequestsInfo.pendingRequests.get(container)).toBe(
|
|
111
|
+
pendingRequest,
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
imageInstances[0].onload?.();
|
|
115
|
+
await expect(secondPromise).resolves.toBeUndefined();
|
|
116
|
+
expect(
|
|
117
|
+
contentInfos.thumbnailRequestsInfo.pendingRequests.get(container),
|
|
118
|
+
).toBeUndefined();
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
function createContentInfos(
|
|
123
|
+
fetchThumbnailDataCallback: (
|
|
124
|
+
periodId: string,
|
|
125
|
+
thumbnailTrackId: string,
|
|
126
|
+
time: number,
|
|
127
|
+
) => Promise<IThumbnailResponseLike>,
|
|
128
|
+
): IPublicApiContentInfos {
|
|
129
|
+
return {
|
|
130
|
+
contentId: "content-1",
|
|
131
|
+
originalUrl: undefined,
|
|
132
|
+
manifest: {} as IPublicApiContentInfos["manifest"],
|
|
133
|
+
currentPeriod: null,
|
|
134
|
+
activeAdaptations: null,
|
|
135
|
+
activeRepresentations: null,
|
|
136
|
+
defaultAudioTrackSwitchingMode: "reload",
|
|
137
|
+
fetchThumbnailDataCallback,
|
|
138
|
+
handledTrackTypes: {
|
|
139
|
+
audio: true,
|
|
140
|
+
video: true,
|
|
141
|
+
text: true,
|
|
142
|
+
},
|
|
143
|
+
initializer: {} as IPublicApiContentInfos["initializer"],
|
|
144
|
+
isDirectFile: false,
|
|
145
|
+
mediaElementTracksStore: null,
|
|
146
|
+
onAudioTracksNotPlayable: "continue",
|
|
147
|
+
onVideoTracksNotPlayable: "continue",
|
|
148
|
+
playbackObserver: {} as IPublicApiContentInfos["playbackObserver"],
|
|
149
|
+
segmentSinkMetricsCallback: null,
|
|
150
|
+
thumbnailRequestsInfo: {
|
|
151
|
+
pendingRequests: new WeakMap(),
|
|
152
|
+
lastResponse: null,
|
|
153
|
+
},
|
|
154
|
+
tracksStore: null,
|
|
155
|
+
useWorker: false,
|
|
156
|
+
currentContentCanceller: new TaskCanceller("content"),
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function makeThumbnailResponse(start: number, end: number): IThumbnailResponseLike {
|
|
161
|
+
return {
|
|
162
|
+
data: new Uint8Array([1, 2, 3]).buffer,
|
|
163
|
+
mimeType: "image/jpeg",
|
|
164
|
+
thumbnails: [
|
|
165
|
+
{
|
|
166
|
+
start,
|
|
167
|
+
end,
|
|
168
|
+
width: 320,
|
|
169
|
+
height: 180,
|
|
170
|
+
offsetX: 0,
|
|
171
|
+
offsetY: 0,
|
|
172
|
+
},
|
|
173
|
+
],
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
interface IThumbnailResponseLike {
|
|
178
|
+
data: ArrayBuffer;
|
|
179
|
+
mimeType: string;
|
|
180
|
+
thumbnails: Array<{
|
|
181
|
+
start: number;
|
|
182
|
+
end: number;
|
|
183
|
+
width: number;
|
|
184
|
+
height: number;
|
|
185
|
+
offsetX: number;
|
|
186
|
+
offsetY: number;
|
|
187
|
+
}>;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
interface FakeImage {
|
|
191
|
+
onload: null | (() => void);
|
|
192
|
+
onerror: null | (() => void);
|
|
193
|
+
}
|
|
@@ -439,7 +439,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
|
|
|
439
439
|
// See: https://bugzilla.mozilla.org/show_bug.cgi?id=1194624
|
|
440
440
|
videoElement.preload = "auto";
|
|
441
441
|
|
|
442
|
-
this.version = /* PLAYER_VERSION */ "4.5.0
|
|
442
|
+
this.version = /* PLAYER_VERSION */ "4.5.0";
|
|
443
443
|
this.log = log;
|
|
444
444
|
this.state = "STOPPED";
|
|
445
445
|
this.videoElement = videoElement;
|
|
@@ -3845,7 +3845,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
|
|
|
3845
3845
|
return true;
|
|
3846
3846
|
}
|
|
3847
3847
|
}
|
|
3848
|
-
Player.version = /* PLAYER_VERSION */ "4.5.0
|
|
3848
|
+
Player.version = /* PLAYER_VERSION */ "4.5.0";
|
|
3849
3849
|
|
|
3850
3850
|
/** Every events sent by the RxPlayer's public API. */
|
|
3851
3851
|
interface IPublicAPIEvent {
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
} from "../../core/types";
|
|
6
6
|
import log from "../../log";
|
|
7
7
|
import noop from "../../utils/noop";
|
|
8
|
+
import queueMicrotaskUtil from "../../utils/queue_microtask";
|
|
8
9
|
import type { IMainThreadMessage } from "../types";
|
|
9
10
|
import CoreInterface from "./base";
|
|
10
11
|
|
|
@@ -18,7 +19,7 @@ export class MonoThreadCoreInterface extends CoreInterface {
|
|
|
18
19
|
|
|
19
20
|
public sendMessage(msg: IMainThreadMessage) {
|
|
20
21
|
log.debug("M-->C", "Sending message", { name: msg.type });
|
|
21
|
-
|
|
22
|
+
queueMicrotaskUtil(() => {
|
|
22
23
|
// NOTE: We don't clone for performance reasons
|
|
23
24
|
this._currentCoreListener({ data: msg });
|
|
24
25
|
});
|
|
@@ -32,7 +33,7 @@ export class MonoThreadCoreInterface extends CoreInterface {
|
|
|
32
33
|
this._currentCoreListener = handler;
|
|
33
34
|
};
|
|
34
35
|
const sendCoreMessage = (msg: ICoreMessage, _transferables?: Transferable[]) => {
|
|
35
|
-
|
|
36
|
+
queueMicrotaskUtil(() => {
|
|
36
37
|
if (msg.type !== CoreMessageType.LogMessage) {
|
|
37
38
|
log.debug("M<--C", "Sending message", { name: msg.type });
|
|
38
39
|
}
|
|
@@ -63,7 +63,9 @@ export default async function renderThumbnail(
|
|
|
63
63
|
const onFinished = () => {
|
|
64
64
|
unlinkCanceller();
|
|
65
65
|
canceller.cancel("thumbnail request finished");
|
|
66
|
-
thumbnailRequestsInfo.pendingRequests.
|
|
66
|
+
if (thumbnailRequestsInfo.pendingRequests.get(container) === canceller) {
|
|
67
|
+
thumbnailRequestsInfo.pendingRequests.delete(container);
|
|
68
|
+
}
|
|
67
69
|
|
|
68
70
|
// Let's revoke the URL after a round-trip to the event loop just in case
|
|
69
71
|
// to prevent revoking before the browser use it.
|
|
@@ -208,16 +210,17 @@ export default async function renderThumbnail(
|
|
|
208
210
|
};
|
|
209
211
|
});
|
|
210
212
|
} catch (srcError) {
|
|
211
|
-
if (options.keepPreviousThumbnailOnError !== true) {
|
|
212
|
-
clearPreviousThumbnails();
|
|
213
|
-
}
|
|
214
213
|
if (srcError !== null && srcError === canceller.signal.cancellationError) {
|
|
214
|
+
onFinished();
|
|
215
215
|
const error = new ThumbnailRenderingError(
|
|
216
216
|
"ABORTED",
|
|
217
217
|
"Thumbnail rendering has been aborted",
|
|
218
218
|
);
|
|
219
219
|
throw error;
|
|
220
220
|
}
|
|
221
|
+
if (options.keepPreviousThumbnailOnError !== true) {
|
|
222
|
+
clearPreviousThumbnails();
|
|
223
|
+
}
|
|
221
224
|
if (srcError instanceof ThumbnailRenderingError) {
|
|
222
225
|
onFinished();
|
|
223
226
|
throw srcError;
|
|
@@ -612,9 +612,9 @@ export default class MediaElementTracksStore extends EventEmitter<IMediaElementT
|
|
|
612
612
|
}
|
|
613
613
|
}
|
|
614
614
|
};
|
|
615
|
-
this._nativeAudioTracks.addEventListener("addtrack", onAddAudioTrack);
|
|
615
|
+
this._nativeAudioTracks.addEventListener?.("addtrack", onAddAudioTrack);
|
|
616
616
|
this.taskCanceller.signal.register(() => {
|
|
617
|
-
this._nativeAudioTracks?.removeEventListener("addtrack", onAddAudioTrack);
|
|
617
|
+
this._nativeAudioTracks?.removeEventListener?.("addtrack", onAddAudioTrack);
|
|
618
618
|
});
|
|
619
619
|
|
|
620
620
|
const onRemoveAudioTrack = () => {
|
|
@@ -633,9 +633,9 @@ export default class MediaElementTracksStore extends EventEmitter<IMediaElementT
|
|
|
633
633
|
}
|
|
634
634
|
};
|
|
635
635
|
|
|
636
|
-
this._nativeAudioTracks.addEventListener("removetrack", onRemoveAudioTrack);
|
|
636
|
+
this._nativeAudioTracks.addEventListener?.("removetrack", onRemoveAudioTrack);
|
|
637
637
|
this.taskCanceller.signal.register(() => {
|
|
638
|
-
this._nativeAudioTracks?.removeEventListener("removetrack", onRemoveAudioTrack);
|
|
638
|
+
this._nativeAudioTracks?.removeEventListener?.("removetrack", onRemoveAudioTrack);
|
|
639
639
|
});
|
|
640
640
|
|
|
641
641
|
const onAudioChange = () => {
|
|
@@ -658,9 +658,9 @@ export default class MediaElementTracksStore extends EventEmitter<IMediaElementT
|
|
|
658
658
|
return;
|
|
659
659
|
};
|
|
660
660
|
|
|
661
|
-
this._nativeAudioTracks.addEventListener("change", onAudioChange);
|
|
661
|
+
this._nativeAudioTracks.addEventListener?.("change", onAudioChange);
|
|
662
662
|
this.taskCanceller.signal.register(() => {
|
|
663
|
-
this._nativeAudioTracks?.removeEventListener("change", onAudioChange);
|
|
663
|
+
this._nativeAudioTracks?.removeEventListener?.("change", onAudioChange);
|
|
664
664
|
});
|
|
665
665
|
}
|
|
666
666
|
|
|
@@ -681,9 +681,9 @@ export default class MediaElementTracksStore extends EventEmitter<IMediaElementT
|
|
|
681
681
|
}
|
|
682
682
|
};
|
|
683
683
|
|
|
684
|
-
this._nativeTextTracks.addEventListener("addtrack", onAddTextTrack);
|
|
684
|
+
this._nativeTextTracks.addEventListener?.("addtrack", onAddTextTrack);
|
|
685
685
|
this.taskCanceller.signal.register(() => {
|
|
686
|
-
this._nativeTextTracks?.removeEventListener("addtrack", onAddTextTrack);
|
|
686
|
+
this._nativeTextTracks?.removeEventListener?.("addtrack", onAddTextTrack);
|
|
687
687
|
});
|
|
688
688
|
|
|
689
689
|
const onRemoveTextTrack = () => {
|
|
@@ -701,9 +701,9 @@ export default class MediaElementTracksStore extends EventEmitter<IMediaElementT
|
|
|
701
701
|
}
|
|
702
702
|
}
|
|
703
703
|
};
|
|
704
|
-
this._nativeTextTracks.addEventListener("removetrack", onRemoveTextTrack);
|
|
704
|
+
this._nativeTextTracks.addEventListener?.("removetrack", onRemoveTextTrack);
|
|
705
705
|
this.taskCanceller.signal.register(() => {
|
|
706
|
-
this._nativeTextTracks?.removeEventListener("removetrack", onRemoveTextTrack);
|
|
706
|
+
this._nativeTextTracks?.removeEventListener?.("removetrack", onRemoveTextTrack);
|
|
707
707
|
});
|
|
708
708
|
|
|
709
709
|
const onTextChange = () => {
|
|
@@ -726,9 +726,9 @@ export default class MediaElementTracksStore extends EventEmitter<IMediaElementT
|
|
|
726
726
|
return;
|
|
727
727
|
};
|
|
728
728
|
|
|
729
|
-
this._nativeTextTracks.addEventListener("change", onTextChange);
|
|
729
|
+
this._nativeTextTracks.addEventListener?.("change", onTextChange);
|
|
730
730
|
this.taskCanceller.signal.register(() => {
|
|
731
|
-
this._nativeTextTracks?.removeEventListener("change", onTextChange);
|
|
731
|
+
this._nativeTextTracks?.removeEventListener?.("change", onTextChange);
|
|
732
732
|
});
|
|
733
733
|
}
|
|
734
734
|
|
|
@@ -749,9 +749,9 @@ export default class MediaElementTracksStore extends EventEmitter<IMediaElementT
|
|
|
749
749
|
}
|
|
750
750
|
};
|
|
751
751
|
|
|
752
|
-
this._nativeVideoTracks.addEventListener("addtrack", onAddVideoTrack);
|
|
752
|
+
this._nativeVideoTracks.addEventListener?.("addtrack", onAddVideoTrack);
|
|
753
753
|
this.taskCanceller.signal.register(() => {
|
|
754
|
-
this._nativeVideoTracks?.removeEventListener("addtrack", onAddVideoTrack);
|
|
754
|
+
this._nativeVideoTracks?.removeEventListener?.("addtrack", onAddVideoTrack);
|
|
755
755
|
});
|
|
756
756
|
|
|
757
757
|
const onRemoveVideoTrack = () => {
|
|
@@ -770,9 +770,9 @@ export default class MediaElementTracksStore extends EventEmitter<IMediaElementT
|
|
|
770
770
|
}
|
|
771
771
|
};
|
|
772
772
|
|
|
773
|
-
this._nativeVideoTracks.addEventListener("removetrack", onRemoveVideoTrack);
|
|
773
|
+
this._nativeVideoTracks.addEventListener?.("removetrack", onRemoveVideoTrack);
|
|
774
774
|
this.taskCanceller.signal.register(() => {
|
|
775
|
-
this._nativeVideoTracks?.removeEventListener("removetrack", onRemoveVideoTrack);
|
|
775
|
+
this._nativeVideoTracks?.removeEventListener?.("removetrack", onRemoveVideoTrack);
|
|
776
776
|
});
|
|
777
777
|
|
|
778
778
|
const onVideoChange = () => {
|
|
@@ -795,9 +795,9 @@ export default class MediaElementTracksStore extends EventEmitter<IMediaElementT
|
|
|
795
795
|
return;
|
|
796
796
|
};
|
|
797
797
|
|
|
798
|
-
this._nativeVideoTracks.addEventListener("change", onVideoChange);
|
|
798
|
+
this._nativeVideoTracks.addEventListener?.("change", onVideoChange);
|
|
799
799
|
this.taskCanceller.signal.register(() => {
|
|
800
|
-
this._nativeVideoTracks?.removeEventListener("change", onVideoChange);
|
|
800
|
+
this._nativeVideoTracks?.removeEventListener?.("change", onVideoChange);
|
|
801
801
|
});
|
|
802
802
|
}
|
|
803
803
|
}
|