@mux/mux-react-native-player 0.1.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.
Files changed (56) hide show
  1. package/MuxReactNativePlayer.podspec +37 -0
  2. package/README.md +134 -0
  3. package/android/build.gradle +33 -0
  4. package/android/src/main/AndroidManifest.xml +1 -0
  5. package/android/src/main/java/com/mux/reactnativeplayer/MuxReactNativePlayerModule.kt +135 -0
  6. package/android/src/main/java/com/mux/reactnativeplayer/MuxVideoRecords.kt +174 -0
  7. package/android/src/main/java/com/mux/reactnativeplayer/MuxVideoView.kt +452 -0
  8. package/android/src/main/res/layout/mux_video_player_view.xml +6 -0
  9. package/assets/MuxRobot_02.gif +0 -0
  10. package/assets/MuxRobot_02@2x.gif +0 -0
  11. package/assets/MuxRobot_03.gif +0 -0
  12. package/assets/MuxRobot_03@2x.gif +0 -0
  13. package/assets/MuxRobot_04.gif +0 -0
  14. package/assets/MuxRobot_04@2x.gif +0 -0
  15. package/assets/MuxRobot_05.gif +0 -0
  16. package/assets/MuxRobot_05@2x.gif +0 -0
  17. package/build/MuxVideoControls.d.ts +21 -0
  18. package/build/MuxVideoControls.d.ts.map +1 -0
  19. package/build/MuxVideoControls.js +1032 -0
  20. package/build/MuxVideoPlayer.d.ts +59 -0
  21. package/build/MuxVideoPlayer.d.ts.map +1 -0
  22. package/build/MuxVideoPlayer.js +265 -0
  23. package/build/MuxVideoView.d.ts +39 -0
  24. package/build/MuxVideoView.d.ts.map +1 -0
  25. package/build/MuxVideoView.js +254 -0
  26. package/build/NativeMuxVideoView.d.ts +5 -0
  27. package/build/NativeMuxVideoView.d.ts.map +1 -0
  28. package/build/NativeMuxVideoView.js +4 -0
  29. package/build/index.d.ts +6 -0
  30. package/build/index.d.ts.map +1 -0
  31. package/build/index.js +3 -0
  32. package/build/normalizeSource.d.ts +7 -0
  33. package/build/normalizeSource.d.ts.map +1 -0
  34. package/build/normalizeSource.js +76 -0
  35. package/build/screenOrientation.d.ts +3 -0
  36. package/build/screenOrientation.d.ts.map +1 -0
  37. package/build/screenOrientation.js +38 -0
  38. package/build/types.d.ts +170 -0
  39. package/build/types.d.ts.map +1 -0
  40. package/build/types.js +1 -0
  41. package/expo-module.config.json +13 -0
  42. package/ios/MuxReactNativePlayerModule.swift +139 -0
  43. package/ios/MuxVideoRecords.swift +212 -0
  44. package/ios/MuxVideoView.swift +502 -0
  45. package/package.json +69 -0
  46. package/plugin/index.d.ts +11 -0
  47. package/plugin/index.js +1 -0
  48. package/plugin/withMuxReactNativePlayer.js +203 -0
  49. package/src/MuxVideoControls.tsx +1772 -0
  50. package/src/MuxVideoPlayer.ts +338 -0
  51. package/src/MuxVideoView.tsx +412 -0
  52. package/src/NativeMuxVideoView.ts +15 -0
  53. package/src/index.ts +32 -0
  54. package/src/normalizeSource.ts +101 -0
  55. package/src/screenOrientation.ts +46 -0
  56. package/src/types.ts +228 -0
@@ -0,0 +1,338 @@
1
+ import type {
2
+ MuxNativeViewRef,
3
+ MuxPlayerStatus,
4
+ MuxPlaybackStatus,
5
+ MuxSourceErrorEvent,
6
+ MuxSourceLoadEvent,
7
+ MuxStatusChangeEvent,
8
+ MuxTimeUpdateEvent,
9
+ MuxVideoSource,
10
+ NormalizedMuxVideoSource,
11
+ } from './types';
12
+ import { normalizeMuxVideoSource } from './normalizeSource';
13
+
14
+ type Listener = () => void;
15
+ type NativeCommand = keyof MuxNativeViewRef;
16
+
17
+ export type MuxVideoPlayerSnapshot = {
18
+ source?: NormalizedMuxVideoSource;
19
+ shouldPlay: boolean;
20
+ muted: boolean;
21
+ volume: number;
22
+ loop: boolean;
23
+ playbackRate: number;
24
+ status: MuxPlayerStatus;
25
+ };
26
+
27
+ const idleStatus: MuxPlayerStatus = {
28
+ status: 'idle',
29
+ currentTime: 0,
30
+ duration: 0,
31
+ bufferedPosition: 0,
32
+ muted: false,
33
+ volume: 1,
34
+ loop: false,
35
+ playbackRate: 1,
36
+ captionTracks: [],
37
+ selectedCaptionTrackId: null,
38
+ };
39
+
40
+ export class MuxVideoPlayer {
41
+ private source?: NormalizedMuxVideoSource;
42
+ private nativeRef?: MuxNativeViewRef | null;
43
+ private listeners = new Set<Listener>();
44
+ private pendingCommands: Array<() => Promise<void>> = [];
45
+ private statusState: MuxPlayerStatus = { ...idleStatus };
46
+ private snapshot: MuxVideoPlayerSnapshot = this.createSnapshot();
47
+ private shouldPlay = false;
48
+ private resumeAt: number | null = null;
49
+
50
+ muted = false;
51
+ volume = 1;
52
+ loop = false;
53
+ playbackRate = 1;
54
+
55
+ constructor(source?: MuxVideoSource) {
56
+ if (source != null) {
57
+ this.source = normalizeMuxVideoSource(source);
58
+ this.statusState = { ...this.statusState, status: 'loading' };
59
+ this.updateSnapshot();
60
+ }
61
+ }
62
+
63
+ get status(): MuxPlaybackStatus {
64
+ return this.statusState.status;
65
+ }
66
+
67
+ get currentTime(): number {
68
+ return this.statusState.currentTime;
69
+ }
70
+
71
+ get duration(): number {
72
+ return this.statusState.duration;
73
+ }
74
+
75
+ get bufferedPosition(): number {
76
+ return this.statusState.bufferedPosition;
77
+ }
78
+
79
+ get error(): string | undefined {
80
+ return this.statusState.error;
81
+ }
82
+
83
+ play(): Promise<void> {
84
+ this.shouldPlay = true;
85
+ this.updateSnapshot();
86
+ this.emitChange();
87
+ return this.runNativeCommand('play');
88
+ }
89
+
90
+ pause(): Promise<void> {
91
+ this.shouldPlay = false;
92
+ this.updateSnapshot();
93
+ this.emitChange();
94
+ return this.runNativeCommand('pause');
95
+ }
96
+
97
+ replay(): Promise<void> {
98
+ this.shouldPlay = true;
99
+ this.updateSnapshot();
100
+ this.emitChange();
101
+ return this.runNativeCommand('replay');
102
+ }
103
+
104
+ seekBy(seconds: number): Promise<void> {
105
+ return this.runNativeCommand('seekBy', seconds);
106
+ }
107
+
108
+ seekTo(seconds: number): Promise<void> {
109
+ return this.runNativeCommand('seekTo', seconds);
110
+ }
111
+
112
+ setMuted(muted: boolean): Promise<void> {
113
+ this.muted = muted;
114
+ this.updateSnapshot();
115
+ this.emitChange();
116
+ return this.runNativeCommand('setMuted', muted);
117
+ }
118
+
119
+ setVolume(volume: number): Promise<void> {
120
+ this.volume = clamp(volume, 0, 1);
121
+ this.updateSnapshot();
122
+ this.emitChange();
123
+ return this.runNativeCommand('setVolume', this.volume);
124
+ }
125
+
126
+ setLoop(loop: boolean): Promise<void> {
127
+ this.loop = loop;
128
+ this.updateSnapshot();
129
+ this.emitChange();
130
+ return this.runNativeCommand('setLoop', loop);
131
+ }
132
+
133
+ setPlaybackRate(rate: number): Promise<void> {
134
+ this.playbackRate = clamp(rate, 0.25, 4);
135
+ this.updateSnapshot();
136
+ this.emitChange();
137
+ return this.runNativeCommand('setPlaybackRate', this.playbackRate);
138
+ }
139
+
140
+ setCaptionTrack(trackId: string | null): Promise<void> {
141
+ this.statusState = {
142
+ ...this.statusState,
143
+ selectedCaptionTrackId: trackId,
144
+ };
145
+ this.updateSnapshot();
146
+ this.emitChange();
147
+ return this.runNativeCommand('setCaptionTrack', trackId);
148
+ }
149
+
150
+ replace(source: MuxVideoSource): void {
151
+ this.source = normalizeMuxVideoSource(source);
152
+ this.resumeAt = null;
153
+ this.statusState = { ...idleStatus, muted: this.muted, volume: this.volume, loop: this.loop, playbackRate: this.playbackRate, status: 'loading' };
154
+ this.updateSnapshot();
155
+ this.emitChange();
156
+ }
157
+
158
+ release(): Promise<void> {
159
+ this.shouldPlay = false;
160
+ this.source = undefined;
161
+ this.resumeAt = null;
162
+ this.statusState = { ...idleStatus };
163
+ this.updateSnapshot();
164
+ this.emitChange();
165
+ return this.runNativeCommand('release');
166
+ }
167
+
168
+ _attachNativeRef(ref: MuxNativeViewRef | null): void {
169
+ this.nativeRef = ref;
170
+ if (!ref || this.pendingCommands.length === 0) {
171
+ return;
172
+ }
173
+
174
+ const pending = this.pendingCommands.splice(0);
175
+ for (const command of pending) {
176
+ command().catch(() => {
177
+ // The caller already received a resolved promise while the command was queued.
178
+ });
179
+ }
180
+ }
181
+
182
+ _subscribe = (listener: Listener): (() => void) => {
183
+ this.listeners.add(listener);
184
+ return () => {
185
+ this.listeners.delete(listener);
186
+ };
187
+ };
188
+
189
+ _getSnapshot = (): MuxVideoPlayerSnapshot => this.snapshot;
190
+
191
+ _handleStatusChange(event: MuxStatusChangeEvent): void {
192
+ this.statusState = {
193
+ ...event,
194
+ captionTracks: event.captionTracks ?? this.statusState.captionTracks ?? [],
195
+ selectedCaptionTrackId: normalizeCaptionTrackId(
196
+ event.selectedCaptionTrackId,
197
+ this.statusState.selectedCaptionTrackId ?? null
198
+ ),
199
+ };
200
+ if (this.statusState.status === 'playing') {
201
+ this.shouldPlay = true;
202
+ } else if (this.statusState.status === 'ended' || this.statusState.status === 'idle' || this.statusState.status === 'error') {
203
+ this.shouldPlay = false;
204
+ }
205
+ this.muted = this.statusState.muted;
206
+ this.volume = this.statusState.volume;
207
+ this.loop = this.statusState.loop;
208
+ this.playbackRate = this.statusState.playbackRate;
209
+ this.updateSnapshot();
210
+ this.emitChange();
211
+ }
212
+
213
+ _handleTimeUpdate(event: MuxTimeUpdateEvent): void {
214
+ this.statusState = {
215
+ ...this.statusState,
216
+ currentTime: event.currentTime,
217
+ duration: event.duration,
218
+ bufferedPosition: event.bufferedPosition,
219
+ };
220
+ this.updateSnapshot();
221
+ this.emitChange();
222
+ }
223
+
224
+ _handleSourceLoad(event: MuxSourceLoadEvent): void {
225
+ const resumeAt = this.resumeAt;
226
+ this.resumeAt = null;
227
+ this.statusState = {
228
+ ...this.statusState,
229
+ status: this.statusState.status === 'loading' ? 'ready' : this.statusState.status,
230
+ duration: event.duration,
231
+ captionTracks: event.captionTracks ?? this.statusState.captionTracks ?? [],
232
+ selectedCaptionTrackId: normalizeCaptionTrackId(
233
+ event.selectedCaptionTrackId,
234
+ this.statusState.selectedCaptionTrackId ?? null
235
+ ),
236
+ error: undefined,
237
+ };
238
+ this.updateSnapshot();
239
+ this.emitChange();
240
+
241
+ if (resumeAt !== null && resumeAt > 0.5 && resumeAt < event.duration - 0.5) {
242
+ this.runNativeCommand('seekTo', resumeAt).catch(() => {
243
+ // Resume seek is best-effort; user can scrub if it fails.
244
+ });
245
+ }
246
+
247
+ if (this.shouldPlay) {
248
+ this.runNativeCommand('play').catch(() => {
249
+ // Source load means the command is retryable through a later user action.
250
+ });
251
+ }
252
+ }
253
+
254
+ _markResumePoint(): void {
255
+ if (this.statusState.currentTime > 0) {
256
+ this.resumeAt = this.statusState.currentTime;
257
+ }
258
+ }
259
+
260
+ _handleSourceError(event: MuxSourceErrorEvent): void {
261
+ this.statusState = {
262
+ ...this.statusState,
263
+ status: 'error',
264
+ error: event.message,
265
+ };
266
+ this.updateSnapshot();
267
+ this.emitChange();
268
+ }
269
+
270
+ private runNativeCommand(command: NativeCommand, value?: number | boolean | string | null): Promise<void> {
271
+ const call = () => {
272
+ const ref = this.nativeRef;
273
+ if (!ref) {
274
+ return Promise.resolve();
275
+ }
276
+ if (typeof ref[command] !== 'function') {
277
+ return Promise.resolve();
278
+ }
279
+ if (value === undefined) {
280
+ return (ref[command] as () => Promise<void>).call(ref);
281
+ }
282
+ return (ref[command] as (argument: number | boolean | string | null) => Promise<void>).call(
283
+ ref,
284
+ value
285
+ );
286
+ };
287
+
288
+ if (!this.nativeRef) {
289
+ this.pendingCommands.push(call);
290
+ return Promise.resolve();
291
+ }
292
+
293
+ return call();
294
+ }
295
+
296
+ private emitChange(): void {
297
+ for (const listener of this.listeners) {
298
+ listener();
299
+ }
300
+ }
301
+
302
+ private createSnapshot(): MuxVideoPlayerSnapshot {
303
+ return {
304
+ source: this.source,
305
+ shouldPlay: this.shouldPlay,
306
+ muted: this.muted,
307
+ volume: this.volume,
308
+ loop: this.loop,
309
+ playbackRate: this.playbackRate,
310
+ status: this.statusState,
311
+ };
312
+ }
313
+
314
+ private updateSnapshot(): void {
315
+ this.snapshot = this.createSnapshot();
316
+ }
317
+ }
318
+
319
+ export function createMuxVideoPlayer(source?: MuxVideoSource): MuxVideoPlayer {
320
+ return new MuxVideoPlayer(source);
321
+ }
322
+
323
+ function clamp(value: number, min: number, max: number): number {
324
+ if (!Number.isFinite(value)) {
325
+ return min;
326
+ }
327
+ return Math.min(max, Math.max(min, value));
328
+ }
329
+
330
+ function normalizeCaptionTrackId(
331
+ trackId: string | null | undefined,
332
+ fallback: string | null
333
+ ): string | null {
334
+ if (trackId === undefined) {
335
+ return fallback;
336
+ }
337
+ return trackId === '' ? null : trackId;
338
+ }