From 93604b2c25f09405fd1765a02405ed5da00e5bcf Mon Sep 17 00:00:00 2001 From: Armands Malejev Date: Wed, 7 Jul 2021 18:59:55 +0300 Subject: [PATCH] VEX-3245: Buffer Progress UI While Paused (#7) Add support for onBufferProgress prop on Android to get buffer data even when the player is paused. --- CHANGELOG.md | 2 + README.md | 13 +++++++ Video.js | 7 ++++ .../exoplayer/ReactExoplayerView.java | 39 +++++++++++++++++++ .../exoplayer/VideoEventEmitter.java | 12 ++++++ 5 files changed, 73 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 731db2e8..3fdf4ea4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ## Changelog +- Add support for `onBufferProgress` on Android for getting buffer data even when the player is paused + - Fix Android AudioFocus bug that could cause player to not respond to play/pause in some instances [#2311](https://github.com/react-native-video/react-native-video/pull/2311) ### Version 5.1.0-alpha9 diff --git a/README.md b/README.md index fdb8231b..d46e8d39 100644 --- a/README.md +++ b/README.md @@ -321,6 +321,7 @@ var styles = StyleSheet.create({ ### Event props * [onAudioBecomingNoisy](#onaudiobecomingnoisy) * [onBandwidthUpdate](#onbandwidthupdate) +* [onBufferProgress](#onbufferprogress) * [onEnd](#onend) * [onExternalPlaybackChange](#onexternalplaybackchange) * [onFullscreenPlayerWillPresent](#onfullscreenplayerwillpresent) @@ -944,6 +945,18 @@ Note: On Android ExoPlayer, you must set the [reportBandwidth](#reportbandwidth) Platforms: Android ExoPlayer + +#### onBufferProgress +Callback function that is called on a set interval which contains the buffer start and end position in ms. + +Payload: +Property | Type | Description +--- | --- | --- +start | number | The buffer start (ms) +end | number | The buffer end (ms) + +Platforms: Android ExoPlayer + #### onEnd Callback function that is called when the player reaches the end of the media. diff --git a/Video.js b/Video.js index f1119150..88d90324 100644 --- a/Video.js +++ b/Video.js @@ -233,6 +233,12 @@ export default class Video extends Component { } }; + _onBufferProgress = (event) => { + if (this.props.onBufferProgress) { + this.props.onBufferProgress(event.nativeEvent); + } + }; + _onGetLicense = (event) => { if (this.props.drm && this.props.drm.getLicense instanceof Function) { const data = event.nativeEvent; @@ -311,6 +317,7 @@ export default class Video extends Component { onVideoSeek: this._onSeek, onVideoEnd: this._onEnd, onVideoBuffer: this._onBuffer, + onVideoBufferProgress: this._onBufferProgress, onVideoBandwidthUpdate: this._onBandwidthUpdate, onTimedMetadata: this._onTimedMetadata, onVideoAudioBecomingNoisy: this._onAudioBecomingNoisy, diff --git a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java index 209cc949..b928ef07 100644 --- a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -81,6 +81,8 @@ import java.util.ArrayList; import java.util.Locale; import java.util.UUID; import java.util.Map; +import java.util.Timer; +import java.util.TimerTask; @SuppressLint("ViewConstructor") class ReactExoplayerView extends FrameLayout implements @@ -137,6 +139,7 @@ class ReactExoplayerView extends FrameLayout implements private int bufferForPlaybackAfterRebufferMs = DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS; private Handler mainHandler; + private Timer bufferCheckTimer; // Props from React private int backBufferDurationMs = DefaultLoadControl.DEFAULT_BACK_BUFFER_DURATION_MS; @@ -415,6 +418,40 @@ class ReactExoplayerView extends FrameLayout implements } } + private void startBufferCheckTimer() { + SimpleExoPlayer player = this.player; + VideoEventEmitter eventEmitter = this.eventEmitter; + Handler mainHandler = this.mainHandler; + + if (this.bufferCheckTimer != null) { + this.stopBufferCheckTimer(); + } + + this.bufferCheckTimer = new Timer(); + TimerTask bufferCheckTimerTask = new TimerTask() { + @Override + public void run() { + if (mainHandler != null) { + mainHandler.post(new Runnable() { + public void run() { + if (player != null) { + double bufferedDuration = (double) (player.getBufferedPercentage() * player.getDuration() / 100); + eventEmitter.bufferProgress(0d, bufferedDuration); + } + } + }); + } + }; + }; + + this.bufferCheckTimer.scheduleAtFixedRate(bufferCheckTimerTask, 500, 1000); + } + + private void stopBufferCheckTimer() { + this.bufferCheckTimer.cancel(); + this.bufferCheckTimer = null; + } + private void initializePlayer() { ReactExoplayerView self = this; // This ensures all props have been settled, to avoid async racing conditions. @@ -506,6 +543,7 @@ class ReactExoplayerView extends FrameLayout implements initializePlayerControl(); setControls(controls); applyModifiers(); + startBufferCheckTimer(); } }, 1); } @@ -597,6 +635,7 @@ class ReactExoplayerView extends FrameLayout implements private void releasePlayer() { if (player != null) { + stopBufferCheckTimer(); updateResumePosition(); player.release(); player.removeMetadataOutput(this); diff --git a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/VideoEventEmitter.java b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/VideoEventEmitter.java index ea0cc5ac..a6d3b6c8 100644 --- a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/VideoEventEmitter.java +++ b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/VideoEventEmitter.java @@ -42,6 +42,7 @@ class VideoEventEmitter { private static final String EVENT_RESUME = "onPlaybackResume"; private static final String EVENT_READY = "onReadyForDisplay"; private static final String EVENT_BUFFER = "onVideoBuffer"; + private static final String EVENT_BUFFER_PROGRESS = "onVideoBufferProgress"; private static final String EVENT_IDLE = "onVideoIdle"; private static final String EVENT_TIMED_METADATA = "onTimedMetadata"; private static final String EVENT_AUDIO_BECOMING_NOISY = "onVideoAudioBecomingNoisy"; @@ -63,6 +64,7 @@ class VideoEventEmitter { EVENT_RESUME, EVENT_READY, EVENT_BUFFER, + EVENT_BUFFER_PROGRESS, EVENT_IDLE, EVENT_TIMED_METADATA, EVENT_AUDIO_BECOMING_NOISY, @@ -87,6 +89,7 @@ class VideoEventEmitter { EVENT_RESUME, EVENT_READY, EVENT_BUFFER, + EVENT_BUFFER_PROGRESS, EVENT_IDLE, EVENT_TIMED_METADATA, EVENT_AUDIO_BECOMING_NOISY, @@ -104,6 +107,8 @@ class VideoEventEmitter { private static final String EVENT_PROP_STEP_FORWARD = "canStepForward"; private static final String EVENT_PROP_STEP_BACKWARD = "canStepBackward"; + private static final String EVENT_PROP_BUFFER_START = "bufferStart"; + private static final String EVENT_PROP_BUFFER_END = "bufferEnd"; private static final String EVENT_PROP_DURATION = "duration"; private static final String EVENT_PROP_PLAYABLE_DURATION = "playableDuration"; private static final String EVENT_PROP_SEEKABLE_DURATION = "seekableDuration"; @@ -206,6 +211,13 @@ class VideoEventEmitter { receiveEvent(EVENT_BUFFER, map); } + void bufferProgress(double start, double end) { + WritableMap map = Arguments.createMap(); + map.putDouble(EVENT_PROP_BUFFER_START, start); + map.putDouble(EVENT_PROP_BUFFER_END, end); + receiveEvent(EVENT_BUFFER_PROGRESS, map); + } + void idle() { receiveEvent(EVENT_IDLE, null); }