From cdbc85638789da0002cdadb13190963d4c1332c2 Mon Sep 17 00:00:00 2001 From: Krzysztof Moch Date: Sat, 4 Nov 2023 18:11:54 +0100 Subject: [PATCH] feat: add `onVolumeChange` event (#3322) * feat: implement `onVolumeChange` event --- CHANGELOG.md | 1 + .../common/react/VideoEventEmitter.java | 9 ++++++++ .../exoplayer/ReactExoplayerView.java | 5 +++++ docs/pages/component/events.md | 22 ++++++++++++++++++- ios/Video/Features/RCTPlayerObserver.swift | 3 +++ ios/Video/RCTVideo.swift | 12 ++++++++++ ios/Video/RCTVideoManager.m | 1 + src/Video.tsx | 9 ++++++++ src/VideoNativeComponent.ts | 5 +++++ src/types/events.ts | 5 +++++ 10 files changed, 71 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6066aeef..b6af3114 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## Changelog ## Next +- Android, iOS: add onVolumeChange event #3322 ### Version 6.0.0-alpha.9 - All: add built-in typescript support [#3266](https://github.com/react-native-video/react-native-video/pull/3266) diff --git a/android/src/main/java/com/brentvatne/common/react/VideoEventEmitter.java b/android/src/main/java/com/brentvatne/common/react/VideoEventEmitter.java index d5506e53..bdbdf0af 100644 --- a/android/src/main/java/com/brentvatne/common/react/VideoEventEmitter.java +++ b/android/src/main/java/com/brentvatne/common/react/VideoEventEmitter.java @@ -50,6 +50,7 @@ public class VideoEventEmitter { private static final String EVENT_AUDIO_BECOMING_NOISY = "onVideoAudioBecomingNoisy"; private static final String EVENT_AUDIO_FOCUS_CHANGE = "onAudioFocusChanged"; private static final String EVENT_PLAYBACK_RATE_CHANGE = "onPlaybackRateChange"; + private static final String EVENT_VOLUME_CHANGE = "onVolumeChange"; private static final String EVENT_AUDIO_TRACKS = "onAudioTracks"; private static final String EVENT_TEXT_TRACKS = "onTextTracks"; private static final String EVENT_VIDEO_TRACKS = "onVideoTracks"; @@ -76,6 +77,7 @@ public class VideoEventEmitter { EVENT_AUDIO_BECOMING_NOISY, EVENT_AUDIO_FOCUS_CHANGE, EVENT_PLAYBACK_RATE_CHANGE, + EVENT_VOLUME_CHANGE, EVENT_AUDIO_TRACKS, EVENT_TEXT_TRACKS, EVENT_VIDEO_TRACKS, @@ -105,6 +107,7 @@ public class VideoEventEmitter { EVENT_AUDIO_BECOMING_NOISY, EVENT_AUDIO_FOCUS_CHANGE, EVENT_PLAYBACK_RATE_CHANGE, + EVENT_VOLUME_CHANGE, EVENT_AUDIO_TRACKS, EVENT_TEXT_TRACKS, EVENT_VIDEO_TRACKS, @@ -140,6 +143,7 @@ public class VideoEventEmitter { private static final String EVENT_PROP_HAS_AUDIO_FOCUS = "hasAudioFocus"; private static final String EVENT_PROP_IS_BUFFERING = "isBuffering"; private static final String EVENT_PROP_PLAYBACK_RATE = "playbackRate"; + private static final String EVENT_PROP_VOLUME = "volume"; private static final String EVENT_PROP_ERROR = "error"; private static final String EVENT_PROP_ERROR_STRING = "errorString"; @@ -379,6 +383,11 @@ public class VideoEventEmitter { receiveEvent(EVENT_PLAYBACK_RATE_CHANGE, map); } + public void volumeChange(float volume) { + WritableMap map = Arguments.createMap(); + map.putDouble(EVENT_PROP_VOLUME, volume); + receiveEvent(EVENT_VOLUME_CHANGE, map); + } public void timedMetadata(ArrayList _metadataArrayList) { if (_metadataArrayList.size() == 0) { diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java index 34e8790b..521b4cfe 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -1434,6 +1434,11 @@ public class ReactExoplayerView extends FrameLayout implements eventEmitter.playbackRateChange(params.speed); } + @Override + public void onVolumeChanged(float volume) { + eventEmitter.volumeChange(volume); + } + @Override public void onIsPlayingChanged(boolean isPlaying) { eventEmitter.playbackStateChanged(isPlaying); diff --git a/docs/pages/component/events.md b/docs/pages/component/events.md index a65c0f3e..ffc44937 100644 --- a/docs/pages/component/events.md +++ b/docs/pages/component/events.md @@ -29,6 +29,7 @@ This page shows the list of available callbacks to handle player notifications | [onTimedMetadata](#ontimedmetadata) | Android, iOS | | [onTextTracks](#ontexttracks) | Android | | [onVideoTracks](#onvideotracks) | Android | +| [onVolumeChange](#onvolumechange) | Android, iOS | ## Details @@ -507,4 +508,23 @@ Example: } ``` -Platforms: Android \ No newline at end of file +Platforms: Android + +### `onVolumeChange` +Callback function that is called when the volume of player changes. +> Note: This event applies to the volume of the player, not the volume of the device. + +Payload: + +Property | Type | Description +--- | --- | --- +volume | number | The volume of the player (between 0 and 1) + +Example: +```javascript +{ + volume: 0.5 +} +``` + +Platforms: Android, iOS \ No newline at end of file diff --git a/ios/Video/Features/RCTPlayerObserver.swift b/ios/Video/Features/RCTPlayerObserver.swift index f7120632..b08b187a 100644 --- a/ios/Video/Features/RCTPlayerObserver.swift +++ b/ios/Video/Features/RCTPlayerObserver.swift @@ -19,6 +19,7 @@ protocol RCTPlayerObserverHandler: RCTPlayerObserverHandlerObjc { func handlePlaybackBufferKeyEmpty(playerItem:AVPlayerItem, change:NSKeyValueObservedChange) func handlePlaybackLikelyToKeepUp(playerItem:AVPlayerItem, change:NSKeyValueObservedChange) func handlePlaybackRateChange(player: AVPlayer, change: NSKeyValueObservedChange) + func handleVolumeChange(player: AVPlayer, change: NSKeyValueObservedChange) func handleExternalPlaybackActiveChange(player: AVPlayer, change: NSKeyValueObservedChange) func handleViewControllerOverlayViewFrameChange(overlayView:UIView, change:NSKeyValueObservedChange) } @@ -74,6 +75,7 @@ class RCTPlayerObserver: NSObject { private var _timeObserver:Any? private var _playerRateChangeObserver:NSKeyValueObservation? + private var _playerVolumeChangeObserver:NSKeyValueObservation? private var _playerExternalPlaybackActiveObserver:NSKeyValueObservation? private var _playerItemStatusObserver:NSKeyValueObservation? private var _playerPlaybackBufferEmptyObserver:NSKeyValueObservation? @@ -95,6 +97,7 @@ class RCTPlayerObserver: NSObject { } _playerRateChangeObserver = player.observe(\.rate, options: [.old], changeHandler: _handlers.handlePlaybackRateChange) + _playerVolumeChangeObserver = player.observe(\.volume, options: [.old] ,changeHandler: _handlers.handleVolumeChange) _playerExternalPlaybackActiveObserver = player.observe(\.isExternalPlaybackActive, changeHandler: _handlers.handleExternalPlaybackActiveChange) } diff --git a/ios/Video/RCTVideo.swift b/ios/Video/RCTVideo.swift index 7696e799..93203346 100644 --- a/ios/Video/RCTVideo.swift +++ b/ios/Video/RCTVideo.swift @@ -107,6 +107,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH @objc var onPlaybackStalled: RCTDirectEventBlock? @objc var onPlaybackResume: RCTDirectEventBlock? @objc var onPlaybackRateChange: RCTDirectEventBlock? + @objc var onVolumeChange: RCTDirectEventBlock? @objc var onVideoPlaybackStateChanged: RCTDirectEventBlock? @objc var onVideoExternalPlaybackChange: RCTDirectEventBlock? @objc var onPictureInPictureStatusChanged: RCTDirectEventBlock? @@ -1237,6 +1238,17 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH _playbackStalled = false } } + + func handleVolumeChange(player: AVPlayer, change: NSKeyValueObservedChange) { + guard let _player = _player else { return } + + if(player.rate == change.oldValue && change.oldValue != nil) { + return + } + + onVolumeChange?(["volume": NSNumber(value: _player.volume), + "target": reactTag as Any]) + } func handleExternalPlaybackActiveChange(player: AVPlayer, change: NSKeyValueObservedChange) { guard let _player = _player else { return } diff --git a/ios/Video/RCTVideoManager.m b/ios/Video/RCTVideoManager.m index a90a5512..a272fa60 100644 --- a/ios/Video/RCTVideoManager.m +++ b/ios/Video/RCTVideoManager.m @@ -57,6 +57,7 @@ RCT_EXPORT_VIEW_PROPERTY(onReadyForDisplay, RCTDirectEventBlock); RCT_EXPORT_VIEW_PROPERTY(onPlaybackStalled, RCTDirectEventBlock); RCT_EXPORT_VIEW_PROPERTY(onPlaybackResume, RCTDirectEventBlock); RCT_EXPORT_VIEW_PROPERTY(onPlaybackRateChange, RCTDirectEventBlock); +RCT_EXPORT_VIEW_PROPERTY(onVolumeChange, RCTDirectEventBlock); RCT_EXPORT_VIEW_PROPERTY(onVideoPlaybackStateChanged, RCTDirectEventBlock); RCT_EXPORT_VIEW_PROPERTY(onVideoExternalPlaybackChange, RCTDirectEventBlock); RCT_EXPORT_VIEW_PROPERTY(onGetLicense, RCTDirectEventBlock); diff --git a/src/Video.tsx b/src/Video.tsx index cf2c82eb..8edb4ca8 100644 --- a/src/Video.tsx +++ b/src/Video.tsx @@ -82,6 +82,7 @@ const Video = forwardRef( onFullscreenPlayerDidDismiss, onReadyForDisplay, onPlaybackRateChange, + onVolumeChange, onAudioBecomingNoisy, onPictureInPictureStatusChanged, onRestoreUserInterfaceForPictureInPictureStop, @@ -344,6 +345,13 @@ const Video = forwardRef( [onPlaybackRateChange], ); + const _onVolumeChange = useCallback( + (e: NativeSyntheticEvent>) => { + onVolumeChange?.(e.nativeEvent); + }, + [onVolumeChange], + ); + const _onReadyForDisplay = useCallback(() => { setShowPoster(false); onReadyForDisplay?.(); @@ -509,6 +517,7 @@ const Video = forwardRef( onAudioFocusChanged={_onAudioFocusChanged} onReadyForDisplay={_onReadyForDisplay} onPlaybackRateChange={_onPlaybackRateChange} + onVolumeChange={_onVolumeChange} onVideoAudioBecomingNoisy={onAudioBecomingNoisy} onPictureInPictureStatusChanged={_onPictureInPictureStatusChanged} onRestoreUserInterfaceForPictureInPictureStop={ diff --git a/src/VideoNativeComponent.ts b/src/VideoNativeComponent.ts index 627c84f6..5c98ab41 100644 --- a/src/VideoNativeComponent.ts +++ b/src/VideoNativeComponent.ts @@ -218,6 +218,10 @@ export type OnPlaybackData = Readonly<{ playbackRate: number; }>; +export type onVolumeChangeData = Readonly<{ + volume: number; +}>; + export type OnExternalPlaybackChangeData = Readonly<{ isExternalPlaybackActive: boolean; }>; @@ -326,6 +330,7 @@ export interface VideoNativeProps extends ViewProps { ) => void; // ios, android onReadyForDisplay?: (event: NativeSyntheticEvent>) => void; onPlaybackRateChange?: (event: NativeSyntheticEvent) => void; // all + onVolumeChange?: (event: NativeSyntheticEvent) => void; // android, ios onVideoExternalPlaybackChange?: ( event: NativeSyntheticEvent, ) => void; diff --git a/src/types/events.ts b/src/types/events.ts index ee6305f9..5cb71717 100644 --- a/src/types/events.ts +++ b/src/types/events.ts @@ -95,6 +95,10 @@ export type OnPlaybackData = Readonly<{ playbackRate: number; }>; +export type OnVolumeChangeData = Readonly<{ + volume: number; +}>; + export type OnExternalPlaybackChangeData = Readonly<{ isExternalPlaybackActive: boolean; }>; @@ -153,6 +157,7 @@ export interface ReactVideoEvents { e: OnPictureInPictureStatusChangedData, ) => void; //iOS onPlaybackRateChange?: (e: OnPlaybackData) => void; //All + onVolumeChange?: (e: OnVolumeChangeData) => void; //Android, iOS onProgress?: (e: OnProgressData) => void; //All onReadyForDisplay?: () => void; //Android, iOS onReceiveAdEvent?: (e: OnReceiveAdEventData) => void; //Android, iOS