@exode-team/react-recorder 1.1.9 → 1.1.10

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/dist/index.js CHANGED
@@ -76,6 +76,61 @@ const stopStream = (stream) => {
76
76
  *
77
77
  * @author: exode <hello@exode.ru>
78
78
  */
79
+ const SEEK_OFFSET = 5;
80
+ const setupMediaSession = (audio, callbacks) => {
81
+ if (!('mediaSession' in navigator)) {
82
+ return;
83
+ }
84
+ navigator.mediaSession.setActionHandler('play', () => {
85
+ audio.play().catch(() => { });
86
+ });
87
+ navigator.mediaSession.setActionHandler('pause', () => {
88
+ audio.pause();
89
+ });
90
+ navigator.mediaSession.setActionHandler('stop', () => {
91
+ var _a;
92
+ audio.pause();
93
+ audio.currentTime = 0;
94
+ (_a = callbacks.onStop) === null || _a === void 0 ? void 0 : _a.call(callbacks);
95
+ });
96
+ navigator.mediaSession.setActionHandler('seekbackward', () => {
97
+ var _a;
98
+ audio.currentTime = Math.max(0, audio.currentTime - SEEK_OFFSET);
99
+ (_a = callbacks.onSeekBackward) === null || _a === void 0 ? void 0 : _a.call(callbacks);
100
+ });
101
+ navigator.mediaSession.setActionHandler('seekforward', () => {
102
+ var _a;
103
+ audio.currentTime = Math.min(audio.duration, audio.currentTime + SEEK_OFFSET);
104
+ (_a = callbacks.onSeekForward) === null || _a === void 0 ? void 0 : _a.call(callbacks);
105
+ });
106
+ navigator.mediaSession.setActionHandler('seekto', (details) => {
107
+ if (details.seekTime != null && isFinite(audio.duration)) {
108
+ audio.currentTime = details.seekTime;
109
+ }
110
+ });
111
+ navigator.mediaSession.setActionHandler('previoustrack', () => {
112
+ var _a;
113
+ (_a = callbacks.onPreviousTrack) === null || _a === void 0 ? void 0 : _a.call(callbacks);
114
+ });
115
+ navigator.mediaSession.setActionHandler('nexttrack', () => {
116
+ var _a;
117
+ (_a = callbacks.onNextTrack) === null || _a === void 0 ? void 0 : _a.call(callbacks);
118
+ });
119
+ };
120
+ const clearMediaSession = () => {
121
+ if (!('mediaSession' in navigator)) {
122
+ return;
123
+ }
124
+ const actions = [
125
+ 'play', 'pause', 'stop',
126
+ 'seekbackward', 'seekforward', 'seekto',
127
+ 'previoustrack', 'nexttrack',
128
+ ];
129
+ actions.forEach((action) => {
130
+ navigator.mediaSession.setActionHandler(action, null);
131
+ });
132
+ navigator.mediaSession.playbackState = 'none';
133
+ };
79
134
  const createAudioPlayback = (url, callbacks) => {
80
135
  const audio = new Audio(url);
81
136
  audio.onloadedmetadata = () => {
@@ -87,6 +142,21 @@ const createAudioPlayback = (url, callbacks) => {
87
142
  audio.onended = () => {
88
143
  callbacks.onEnded();
89
144
  };
145
+ audio.onplay = () => {
146
+ var _a;
147
+ setupMediaSession(audio, callbacks);
148
+ if ('mediaSession' in navigator) {
149
+ navigator.mediaSession.playbackState = 'playing';
150
+ }
151
+ (_a = callbacks.onPlay) === null || _a === void 0 ? void 0 : _a.call(callbacks);
152
+ };
153
+ audio.onpause = () => {
154
+ var _a;
155
+ if ('mediaSession' in navigator) {
156
+ navigator.mediaSession.playbackState = 'paused';
157
+ }
158
+ (_a = callbacks.onPause) === null || _a === void 0 ? void 0 : _a.call(callbacks);
159
+ };
90
160
  return { audio, url };
91
161
  };
92
162
  const cleanupAudio = (audio) => {
@@ -95,6 +165,9 @@ const cleanupAudio = (audio) => {
95
165
  audio.onended = null;
96
166
  audio.ontimeupdate = null;
97
167
  audio.onloadedmetadata = null;
168
+ audio.onplay = null;
169
+ audio.onpause = null;
170
+ clearMediaSession();
98
171
  };
99
172
  const createObjectURL = (blob) => {
100
173
  return URL.createObjectURL(blob);
@@ -364,6 +437,8 @@ const useAudioPlayback = () => {
364
437
  setIsPlaying(false);
365
438
  setPlaybackTime(0);
366
439
  },
440
+ onPlay: () => setIsPlaying(true),
441
+ onPause: () => setIsPlaying(false),
367
442
  });
368
443
  audioRef.current = audio;
369
444
  setPlaybackTime(0);
@@ -383,6 +458,8 @@ const useAudioPlayback = () => {
383
458
  setIsPlaying(false);
384
459
  setPlaybackTime(0);
385
460
  },
461
+ onPlay: () => setIsPlaying(true),
462
+ onPause: () => setIsPlaying(false),
386
463
  });
387
464
  audioRef.current = audio;
388
465
  }
@@ -823,6 +900,13 @@ const useAudioBlobLoader = (options) => {
823
900
  onLoadedMetadata: (dur) => callbacksRef.current.onLoadedMetadata(dur),
824
901
  onTimeUpdate: (time) => callbacksRef.current.onTimeUpdate(time),
825
902
  onEnded: () => callbacksRef.current.onEnded(),
903
+ onPlay: () => { var _a, _b; return (_b = (_a = callbacksRef.current).onPlay) === null || _b === void 0 ? void 0 : _b.call(_a); },
904
+ onPause: () => { var _a, _b; return (_b = (_a = callbacksRef.current).onPause) === null || _b === void 0 ? void 0 : _b.call(_a); },
905
+ onStop: () => { var _a, _b; return (_b = (_a = callbacksRef.current).onStop) === null || _b === void 0 ? void 0 : _b.call(_a); },
906
+ onSeekBackward: () => { var _a, _b; return (_b = (_a = callbacksRef.current).onSeekBackward) === null || _b === void 0 ? void 0 : _b.call(_a); },
907
+ onSeekForward: () => { var _a, _b; return (_b = (_a = callbacksRef.current).onSeekForward) === null || _b === void 0 ? void 0 : _b.call(_a); },
908
+ onPreviousTrack: () => { var _a, _b; return (_b = (_a = callbacksRef.current).onPreviousTrack) === null || _b === void 0 ? void 0 : _b.call(_a); },
909
+ onNextTrack: () => { var _a, _b; return (_b = (_a = callbacksRef.current).onNextTrack) === null || _b === void 0 ? void 0 : _b.call(_a); },
826
910
  });
827
911
  audioRef.current = audio;
828
912
  setIsLoaded(true);
@@ -953,9 +1037,13 @@ const audioPlaybackManager = {
953
1037
  * @author: exode <hello@exode.ru>
954
1038
  */
955
1039
  const useVoiceMessagePlayer = (options) => {
956
- const { src, onEnded, waveformData, preloadedBlob, duration: externalDuration, } = options;
1040
+ const { src, onEnded, onPreviousTrack, onNextTrack, waveformData, preloadedBlob, duration: externalDuration, } = options;
957
1041
  const onEndedRef = useRef(onEnded);
1042
+ const onPreviousTrackRef = useRef(onPreviousTrack);
1043
+ const onNextTrackRef = useRef(onNextTrack);
958
1044
  onEndedRef.current = onEnded;
1045
+ onPreviousTrackRef.current = onPreviousTrack;
1046
+ onNextTrackRef.current = onNextTrack;
959
1047
  const [isPlaying, setIsPlaying] = useState(false);
960
1048
  const [currentTime, setCurrentTime] = useState(0);
961
1049
  const [duration, setDuration] = useState(externalDuration || 0);
@@ -989,6 +1077,14 @@ const useVoiceMessagePlayer = (options) => {
989
1077
  setCurrentTime(0);
990
1078
  (_a = onEndedRef.current) === null || _a === void 0 ? void 0 : _a.call(onEndedRef);
991
1079
  },
1080
+ onPlay: () => setIsPlaying(true),
1081
+ onPause: () => setIsPlaying(false),
1082
+ onStop: () => {
1083
+ setIsPlaying(false);
1084
+ setCurrentTime(0);
1085
+ },
1086
+ onPreviousTrack: () => { var _a; return (_a = onPreviousTrackRef.current) === null || _a === void 0 ? void 0 : _a.call(onPreviousTrackRef); },
1087
+ onNextTrack: () => { var _a; return (_a = onNextTrackRef.current) === null || _a === void 0 ? void 0 : _a.call(onNextTrackRef); },
992
1088
  },
993
1089
  });
994
1090
  /** Pre-fetch audio without playing (for viewport preloading) */
@@ -2136,6 +2232,13 @@ class VoicePlaybackChainClass {
2136
2232
  this.entries[idx + 1].play();
2137
2233
  }
2138
2234
  };
2235
+ /** Play the previous voice message before the given media ID */
2236
+ this.playPrevious = (currentId) => {
2237
+ const idx = this.entries.findIndex((e) => e.id === currentId);
2238
+ if (idx > 0) {
2239
+ this.entries[idx - 1].play();
2240
+ }
2241
+ };
2139
2242
  }
2140
2243
  }
2141
2244
  const VoicePlaybackChain = new VoicePlaybackChainClass();
@@ -2243,12 +2346,24 @@ const VoiceMessagePlayer = React.forwardRef((props, ref) => {
2243
2346
  VoicePlaybackChain.playNext(chainId);
2244
2347
  }
2245
2348
  }, [onEnded, chainId]);
2349
+ const handleNextTrack = useCallback(() => {
2350
+ if (chainId != null) {
2351
+ VoicePlaybackChain.playNext(chainId);
2352
+ }
2353
+ }, [chainId]);
2354
+ const handlePreviousTrack = useCallback(() => {
2355
+ if (chainId != null) {
2356
+ VoicePlaybackChain.playPrevious(chainId);
2357
+ }
2358
+ }, [chainId]);
2246
2359
  const { play, progress, isLoaded, isPlaying, isLoading, currentTime, waveformLevels, seekTo, seekToTime, prefetch, preload, formatTime, togglePlayback, duration: resolvedDuration, } = useVoiceMessagePlayer({
2247
2360
  src,
2248
2361
  duration,
2249
2362
  waveformData,
2250
2363
  preloadedBlob: props.preloadedBlob,
2251
2364
  onEnded: handleEnded,
2365
+ onNextTrack: handleNextTrack,
2366
+ onPreviousTrack: handlePreviousTrack,
2252
2367
  });
2253
2368
  useImperativeHandle(ref, () => ({ play, prefetch, seekToTime }), [
2254
2369
  play,