From fac547e7e67c16f250a17f51c9ba40717c57053a Mon Sep 17 00:00:00 2001 From: BASTIEN Valentin Date: Thu, 29 Nov 2018 17:44:59 +0100 Subject: [PATCH 1/8] remove unnecessary view inflate --- .../com/inprogress/reactnativeyoutube/YouTubeView.java | 1 - android/src/main/res/layout/youtube_layout.xml | 7 ------- 2 files changed, 8 deletions(-) delete mode 100644 android/src/main/res/layout/youtube_layout.xml diff --git a/android/src/main/java/com/inprogress/reactnativeyoutube/YouTubeView.java b/android/src/main/java/com/inprogress/reactnativeyoutube/YouTubeView.java index ecf8847a..8bc912cd 100644 --- a/android/src/main/java/com/inprogress/reactnativeyoutube/YouTubeView.java +++ b/android/src/main/java/com/inprogress/reactnativeyoutube/YouTubeView.java @@ -29,7 +29,6 @@ public ReactContext getReactContext() { } public void init() { - inflate(getContext(), R.layout.youtube_layout, this); mVideoFragment = VideoFragment.newInstance(this); mYouTubeController = new YouTubePlayerController(this); } diff --git a/android/src/main/res/layout/youtube_layout.xml b/android/src/main/res/layout/youtube_layout.xml deleted file mode 100644 index 384d74b5..00000000 --- a/android/src/main/res/layout/youtube_layout.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - From e064a364540cc00d1c7d2314016f1fedad0d6e1e Mon Sep 17 00:00:00 2001 From: BASTIEN Valentin Date: Fri, 30 Nov 2018 09:20:18 +0100 Subject: [PATCH 2/8] implements initialize after ondestroy view event --- .../reactnativeyoutube/VideoFragment.java | 85 +++++++++++- .../YouTubePlayerController.java | 122 +++++++++++++++--- .../reactnativeyoutube/YouTubeView.java | 14 ++ 3 files changed, 200 insertions(+), 21 deletions(-) diff --git a/android/src/main/java/com/inprogress/reactnativeyoutube/VideoFragment.java b/android/src/main/java/com/inprogress/reactnativeyoutube/VideoFragment.java index 64e2dc8d..b1b99cf3 100644 --- a/android/src/main/java/com/inprogress/reactnativeyoutube/VideoFragment.java +++ b/android/src/main/java/com/inprogress/reactnativeyoutube/VideoFragment.java @@ -1,11 +1,22 @@ package com.inprogress.reactnativeyoutube; +import android.view.LayoutInflater; +import android.view.ViewGroup; +import android.os.Bundle; +import android.view.View; + +import com.google.android.youtube.player.YouTubeInitializationResult; import com.google.android.youtube.player.YouTubePlayerFragment; +import com.google.android.youtube.player.YouTubePlayer; -public class VideoFragment extends YouTubePlayerFragment { +public class VideoFragment extends YouTubePlayerFragment implements YouTubePlayer.OnInitializedListener { private YouTubeView mYouTubeView; + private boolean mPlayerReleased = true; + + private String mApiKey = null; + private YouTubePlayer.OnInitializedListener mInitializationListener = null; public VideoFragment() {} @@ -28,4 +39,76 @@ public void onResume() { super.onResume(); } + + @Override + public View onCreateView (LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View v = super.onCreateView(inflater, container, savedInstanceState); + /* + * According to the Youtube Api documentation : + * The YouTubePlayer associated with this fragment will be released + * whenever its onDestroyView() method is called. + * + * If we do not initialize again the player, it will crash telling that + * the youtube player has been released + */ + if (mPlayerReleased) { + initialize(mApiKey, mInitializationListener); + /* Now that the player has been initialize we track that we can use it */ + mPlayerReleased = false; + } + return v; + } + + @Override + public void onDestroyView() { + /* + * According to youtube Player api : + * The YouTubePlayer associated with this fragment will be released + * whenever its onDestroyView() method is called. + * + * This is important since it cause Exception : The youtube player has been release + * on some cases + * + * We keep track of this release in order to do a proper initialization + * back again at view creation + */ + mPlayerReleased = true; + /* + * Tell out view that the player is release in order to stop all actions + * on the youtube player until next initialization + */ + mYouTubeView.onPlayerRelease(); + super.onDestroyView(); + } + + @Override + public void initialize(String developerKey, YouTubePlayer.OnInitializedListener listener) { + /* + * Inform the player that an initialization has started + */ + mYouTubeView.onInitializationStarted(); + /* + * Save the developer key and the listener for later use + * (in onCreateView after a onDestroyView occurs) + */ + mApiKey = developerKey; + mInitializationListener = listener; + + /* Now we call the actual API */ + super.initialize(developerKey, this); + } + + @Override + public void onInitializationSuccess(YouTubePlayer.Provider provider, YouTubePlayer youTubePlayer, boolean wasRestored) { + if (mInitializationListener != null) mInitializationListener.onInitializationSuccess(provider, youTubePlayer, wasRestored); + /* Keep track that the player is now available */ + mPlayerReleased = false; + } + + @Override + public void onInitializationFailure(YouTubePlayer.Provider provider, YouTubeInitializationResult result) { + if (mInitializationListener != null) mInitializationListener.onInitializationFailure(provider, result); + /* Keep track that the player is not available */ + mPlayerReleased = true; + } } diff --git a/android/src/main/java/com/inprogress/reactnativeyoutube/YouTubePlayerController.java b/android/src/main/java/com/inprogress/reactnativeyoutube/YouTubePlayerController.java index efacfff1..e224ba20 100644 --- a/android/src/main/java/com/inprogress/reactnativeyoutube/YouTubePlayerController.java +++ b/android/src/main/java/com/inprogress/reactnativeyoutube/YouTubePlayerController.java @@ -43,6 +43,14 @@ public class YouTubePlayerController implements private boolean mShowFullscreenButton = true; private boolean mResumePlay = true; + /** + * Tells if the player is available to request + * @return true if the player is available, false if currently initializing + * or false if the player has been released + */ + private boolean mPlayerAvailable = false; + + public YouTubePlayerController(YouTubeView youTubeView) { mYouTubeView = youTubeView; } @@ -51,6 +59,12 @@ public YouTubePlayerController(YouTubeView youTubeView) { public void onInitializationSuccess(YouTubePlayer.Provider provider, YouTubePlayer youTubePlayer, boolean wasRestored) { if (!wasRestored) { mYouTubePlayer = youTubePlayer; + + /* + * Now we are sure that the player is available + */ + mPlayerAvailable = true; + mYouTubePlayer.setPlayerStateChangeListener(this); mYouTubePlayer.setPlaybackEventListener(this); mYouTubePlayer.setOnFullscreenListener(this); @@ -114,6 +128,7 @@ public void onSeekTo(int i) { @Override public void onLoading() { + setLoaded(false); mYouTubeView.didChangeToState("loading"); } @@ -126,6 +141,7 @@ public void onLoaded(String videoId) { setLoaded(true); mIsReady = true; } + automaticPlay(); } @Override @@ -133,6 +149,18 @@ public void onAdStarted() { mYouTubeView.didChangeToState("adStarted"); } + /** + * If video is loaded and play is requested (isPlay()) + * It will start the video + */ + private void automaticPlay() { + if (isLoaded() && isPlay()) { + mYouTubePlayer.play(); + } else if (!isPlay()) { + mYouTubePlayer.pause(); + } + } + @Override public void onVideoStarted() { mYouTubeView.didChangeToState("started"); @@ -158,18 +186,22 @@ public void onError(YouTubePlayer.ErrorReason errorReason) { } public void seekTo(int second) { + if (!isPlayerAvailable()) return; if (isLoaded()) mYouTubePlayer.seekToMillis(second * 1000); } public int getCurrentTime() { + if (!isPlayerAvailable()) return 0; return mYouTubePlayer.getCurrentTimeMillis() / 1000; } public int getDuration() { + if (!isPlayerAvailable()) return 0; return mYouTubePlayer.getDurationMillis() / 1000; } public void nextVideo() { + if (!isPlayerAvailable()) return; if (isLoaded()) { if (mYouTubePlayer.hasNext()) mYouTubePlayer.next(); else if (isLoop()) { @@ -181,6 +213,7 @@ else if (isLoop()) { } public void previousVideo() { + if (!isPlayerAvailable()) return; if (isLoaded()) { if (mYouTubePlayer.hasPrevious()) mYouTubePlayer.previous(); else if (isLoop()) { @@ -192,6 +225,7 @@ else if (isLoop()) { } public void playVideoAt(int index) { + if (!isPlayerAvailable()) return; if (isLoaded() && isVideosMode()) { boolean indexIsInRange = setVideosIndex(index); if (indexIsInRange) loadVideos(); @@ -204,26 +238,43 @@ public void playVideoAt(int index) { **/ private void loadVideo() { - if (isPlay()) mYouTubePlayer.loadVideo(mVideoId); - else mYouTubePlayer.cueVideo(mVideoId); + if (!isPlayerAvailable()) return; + + /* + * we only cue the video in order to load it + * when the video will be loaded depending on the isPlay() boolean we + * will start it right away or wait for an user interaction + */ + mYouTubePlayer.cueVideo(mVideoId); setVideosIndex(0); setVideoMode(); } private void loadVideos() { - if (isPlay()) mYouTubePlayer.loadVideos(mVideoIds, getVideosIndex(), 0); - else mYouTubePlayer.cueVideos(mVideoIds, getVideosIndex(), 0); + if (!isPlayerAvailable()) return; + /* + * we only cue the video in order to load it + * when the video will be loaded depending on the isPlay() boolean we + * will start it right away or wait for an user interaction + */ + mYouTubePlayer.cueVideos(mVideoIds, getVideosIndex(), 0); setVideosMode(); } private void loadPlaylist() { - if (isPlay()) mYouTubePlayer.loadPlaylist(mPlaylistId); - else mYouTubePlayer.cuePlaylist(mPlaylistId); + if (!isPlayerAvailable()) return; + /* + * we only cue the playlist in order to load it + * when the video will be loaded depending on the isPlay() boolean we + * will start it right away or wait for an user interaction + */ + mYouTubePlayer.cuePlaylist(mPlaylistId); setVideosIndex(0); setPlaylistMode(); } private void updateControls() { + if (!isPlayerAvailable()) return; switch (mControls) { case 0: mYouTubePlayer.setPlayerStyle(YouTubePlayer.PlayerStyle.CHROMELESS); @@ -238,10 +289,12 @@ private void updateControls() { } private void updateFullscreen() { + if (!isPlayerAvailable()) return; mYouTubePlayer.setFullscreen(mFullscreen); } private void updateShowFullscreenButton() { + if (!isPlayerAvailable()) return; mYouTubePlayer.setShowFullscreenButton(mShowFullscreenButton); } @@ -306,16 +359,17 @@ public int getVideosIndex() { } public void onVideoFragmentResume() { - if (isResumePlay() && mYouTubePlayer != null) { - // For some reason calling mYouTubePlayer.play() right away is ineffective - final Handler handler = new Handler(); - handler.postDelayed(new Runnable() { - @Override - public void run() { - mYouTubePlayer.play(); - } - }, 1); - } + // if (!isPlayerAvailable()) return; + // if (isResumePlay() && mYouTubePlayer != null) { + // // For some reason calling mYouTubePlayer.play() right away is ineffective + // final Handler handler = new Handler(); + // handler.postDelayed(new Runnable() { + // @Override + // public void run() { + // mYouTubePlayer.play(); + // } + // }, 1); + // } } private boolean isPlay() { @@ -334,6 +388,15 @@ private int getControls() { return mControls; } + /** + * Tells if the player is available to request + * @return true if the player is available, false if currently initializing + * or false if the player has been released + */ + public boolean isPlayerAvailable() { + return mPlayerAvailable; + } + private boolean isResumePlay() { return mResumePlay; } @@ -344,10 +407,12 @@ private boolean isResumePlay() { public void setVideoId(String videoId) { mVideoId = videoId; + if (!isPlayerAvailable()) return; if (isLoaded()) loadVideo(); } public void setVideoIds(ReadableArray videoIds) { + if (!isPlayerAvailable()) return; if (videoIds != null) { setVideosIndex(0); mVideoIds.clear(); @@ -360,15 +425,13 @@ public void setVideoIds(ReadableArray videoIds) { public void setPlaylistId(String playlistId) { mPlaylistId = playlistId; + if (!isPlayerAvailable()) return; if (isLoaded()) loadPlaylist(); } public void setPlay(boolean play) { mPlay = play; - if (isLoaded()) { - if (isPlay()) mYouTubePlayer.play(); - else mYouTubePlayer.pause(); - } + automaticPlay(); } public void setLoop(boolean loop) { @@ -377,6 +440,7 @@ public void setLoop(boolean loop) { public void setFullscreen(boolean fullscreen) { mFullscreen = fullscreen; + if (!isPlayerAvailable()) return; if (isLoaded()) updateFullscreen(); } @@ -388,6 +452,7 @@ public void setControls(int controls) { } public void setShowFullscreenButton(boolean show) { + if (!isPlayerAvailable()) return; mShowFullscreenButton = show; if (isLoaded()) updateShowFullscreenButton(); } @@ -395,4 +460,21 @@ public void setShowFullscreenButton(boolean show) { public void setResumePlay(boolean resumePlay) { mResumePlay = resumePlay; } + + + public void onInitializationStarted() { + /* + * Initialization is in progress, we should not query the player + * during this time + */ + mPlayerAvailable = false; + } + + /** + * Called when the youtube fragment has destroyed its view + * And so release its player based on documentation + */ + public void onPlayerRelease() { + mPlayerAvailable = false; + } } diff --git a/android/src/main/java/com/inprogress/reactnativeyoutube/YouTubeView.java b/android/src/main/java/com/inprogress/reactnativeyoutube/YouTubeView.java index 8bc912cd..eb93f6f4 100644 --- a/android/src/main/java/com/inprogress/reactnativeyoutube/YouTubeView.java +++ b/android/src/main/java/com/inprogress/reactnativeyoutube/YouTubeView.java @@ -195,4 +195,18 @@ public void setShowFullscreenButton(boolean bool) { public void setResumePlay(boolean bool) { mYouTubeController.setResumePlay(bool); } + + /** + * Spread the onInitializationStarted event to the controller + */ + public void onInitializationStarted() { + mYouTubeController.onInitializationStarted(); + } + + /** + * Spread the player released event to the controller + */ + public void onPlayerRelease() { + mYouTubeController.onPlayerRelease(); + } } From cda501ae1f091992c1af315b8e1aba94d553a5e0 Mon Sep 17 00:00:00 2001 From: BASTIEN Valentin Date: Fri, 30 Nov 2018 10:53:10 +0100 Subject: [PATCH 3/8] when the player is released, it can't play, we update the state in consequence --- .../inprogress/reactnativeyoutube/YouTubePlayerController.java | 1 + 1 file changed, 1 insertion(+) diff --git a/android/src/main/java/com/inprogress/reactnativeyoutube/YouTubePlayerController.java b/android/src/main/java/com/inprogress/reactnativeyoutube/YouTubePlayerController.java index e224ba20..4dea9b3a 100644 --- a/android/src/main/java/com/inprogress/reactnativeyoutube/YouTubePlayerController.java +++ b/android/src/main/java/com/inprogress/reactnativeyoutube/YouTubePlayerController.java @@ -476,5 +476,6 @@ public void onInitializationStarted() { */ public void onPlayerRelease() { mPlayerAvailable = false; + mPlay = false; } } From 38bc24917bc13201c5661f08ec06c5f02059ccca Mon Sep 17 00:00:00 2001 From: BASTIEN Valentin Date: Fri, 30 Nov 2018 10:53:47 +0100 Subject: [PATCH 4/8] update the playing state if stop or paused is send spontaneously from the library --- example/RCTYouTubeExample.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/example/RCTYouTubeExample.js b/example/RCTYouTubeExample.js index 50e4f12f..21a7a29d 100644 --- a/example/RCTYouTubeExample.js +++ b/example/RCTYouTubeExample.js @@ -63,7 +63,10 @@ export default class RCTYouTubeExample extends React.Component { ]} onError={e => this.setState({ error: e.error })} onReady={e => this.setState({ isReady: true })} - onChangeState={e => this.setState({ status: e.state })} + onChangeState={e => this.setState((prevState) => ({ + status: e.state, + isPlaying: e.state === 'stopped' || e.state === 'paused' ? false : prevState.isPlaying + }))} onChangeQuality={e => this.setState({ quality: e.quality })} onChangeFullscreen={e => this.setState({ fullscreen: e.isFullscreen })} onProgress={e => this.setState({ duration: e.duration, currentTime: e.currentTime })} From 011aa8d36eb28fcb6fc50d257a7272431481d124 Mon Sep 17 00:00:00 2001 From: BASTIEN Valentin Date: Fri, 30 Nov 2018 10:54:17 +0100 Subject: [PATCH 5/8] save video ids even if player is not ready yet --- .../inprogress/reactnativeyoutube/YouTubePlayerController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/main/java/com/inprogress/reactnativeyoutube/YouTubePlayerController.java b/android/src/main/java/com/inprogress/reactnativeyoutube/YouTubePlayerController.java index 4dea9b3a..d26f5c14 100644 --- a/android/src/main/java/com/inprogress/reactnativeyoutube/YouTubePlayerController.java +++ b/android/src/main/java/com/inprogress/reactnativeyoutube/YouTubePlayerController.java @@ -412,13 +412,13 @@ public void setVideoId(String videoId) { } public void setVideoIds(ReadableArray videoIds) { - if (!isPlayerAvailable()) return; if (videoIds != null) { setVideosIndex(0); mVideoIds.clear(); for (int i = 0; i < videoIds.size(); i++) { mVideoIds.add(videoIds.getString(i)); } + if (!isPlayerAvailable()) return; if (isLoaded()) loadVideos(); } } From 137de4264836682fb35344726a25b9781fe2fbaa Mon Sep 17 00:00:00 2001 From: BASTIEN Valentin Date: Fri, 30 Nov 2018 10:54:55 +0100 Subject: [PATCH 6/8] remove possibly unneccessary condition --- .../reactnativeyoutube/YouTubePlayerController.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/android/src/main/java/com/inprogress/reactnativeyoutube/YouTubePlayerController.java b/android/src/main/java/com/inprogress/reactnativeyoutube/YouTubePlayerController.java index d26f5c14..27306fc5 100644 --- a/android/src/main/java/com/inprogress/reactnativeyoutube/YouTubePlayerController.java +++ b/android/src/main/java/com/inprogress/reactnativeyoutube/YouTubePlayerController.java @@ -136,11 +136,9 @@ public void onLoading() { public void onLoaded(String videoId) { if (isVideosMode()) setVideosIndex(mVideoIds.indexOf(videoId)); - if (!mIsReady) { - mYouTubeView.playerViewDidBecomeReady(); - setLoaded(true); - mIsReady = true; - } + mYouTubeView.playerViewDidBecomeReady(); + setLoaded(true); + mIsReady = true; automaticPlay(); } From 20f680df3d17bdd1da2d633051634df1b8706bb8 Mon Sep 17 00:00:00 2001 From: BASTIEN Valentin Date: Fri, 30 Nov 2018 10:59:22 +0100 Subject: [PATCH 7/8] save the current video id when loaded (in case of next or previous video) --- .../inprogress/reactnativeyoutube/YouTubePlayerController.java | 1 + 1 file changed, 1 insertion(+) diff --git a/android/src/main/java/com/inprogress/reactnativeyoutube/YouTubePlayerController.java b/android/src/main/java/com/inprogress/reactnativeyoutube/YouTubePlayerController.java index 27306fc5..dfaa1e8b 100644 --- a/android/src/main/java/com/inprogress/reactnativeyoutube/YouTubePlayerController.java +++ b/android/src/main/java/com/inprogress/reactnativeyoutube/YouTubePlayerController.java @@ -134,6 +134,7 @@ public void onLoading() { @Override public void onLoaded(String videoId) { + mVideoId = videoId; if (isVideosMode()) setVideosIndex(mVideoIds.indexOf(videoId)); mYouTubeView.playerViewDidBecomeReady(); From ecbeb431c8de38f3641d766fa33d4548fc9be3b5 Mon Sep 17 00:00:00 2001 From: BASTIEN Valentin Date: Fri, 30 Nov 2018 15:04:33 +0100 Subject: [PATCH 8/8] do a ugly hack to restore resume on android --- .../YouTubePlayerController.java | 45 ++++++++++++++----- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/android/src/main/java/com/inprogress/reactnativeyoutube/YouTubePlayerController.java b/android/src/main/java/com/inprogress/reactnativeyoutube/YouTubePlayerController.java index dfaa1e8b..389eb8ac 100644 --- a/android/src/main/java/com/inprogress/reactnativeyoutube/YouTubePlayerController.java +++ b/android/src/main/java/com/inprogress/reactnativeyoutube/YouTubePlayerController.java @@ -42,6 +42,7 @@ public class YouTubePlayerController implements private int mControls = 1; private boolean mShowFullscreenButton = true; private boolean mResumePlay = true; + private boolean mJustResumed = false; /** * Tells if the player is available to request @@ -99,6 +100,24 @@ public void onPaused() { @Override public void onStopped() { mYouTubeView.didChangeToState("stopped"); + /** + * ugly hack to trigger automatic video resume after the fragment went down + * @see {@link #onVideoFragmentResume()} for more details + */ + if (mJustResumed) { // check if the fragment just resumed + if (isResumePlay()) { // check if the config ask use to do an automatic resume + /* + * check if the video is already loaded, if so play the video directly + * (the onLoaded event won't trigger so we cannot use it to play the video) + * or + * load the video (the onLoaded event will then take care of playing that video) + */ + if (isLoaded()) mYouTubePlayer.play(); + else loadVideo(); + } + // we consume the flag of resuming + mJustResumed = false; + } } @Override @@ -358,17 +377,21 @@ public int getVideosIndex() { } public void onVideoFragmentResume() { - // if (!isPlayerAvailable()) return; - // if (isResumePlay() && mYouTubePlayer != null) { - // // For some reason calling mYouTubePlayer.play() right away is ineffective - // final Handler handler = new Handler(); - // handler.postDelayed(new Runnable() { - // @Override - // public void run() { - // mYouTubePlayer.play(); - // } - // }, 1); - // } + /** + * we tell that the fragment just resume + * in order to resume the video if the configuration allows it (resumePlayAndroid to true) + * + * /!\ We cannot just do a player.play() because of the youtube player api + * Indeed this library at each resume of the fragment trigger its current state + * It seems that this event is always "stopped" (probably because of the youtube player view not visible) + * In this case if we do a regular youtubeplayer.play(), the stop event will directly stop + * the play and stay as is. + * + * This is why We do a hack in the {@link #onStopped()} event on the youtube player library + * looking if the fragment just resumed and if so, will ask for a play if the configs allows to. + * @see {@link #onStopped()} + */ + mJustResumed = true; } private boolean isPlay() {