Skip to content

Commit

Permalink
iOS: Reduce possible race conditions
Browse files Browse the repository at this point in the history
  • Loading branch information
dasisdormax committed Oct 19, 2023
1 parent 87d4158 commit 62506d1
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 23 deletions.
55 changes: 34 additions & 21 deletions gdx-video-robovm/src/com/badlogic/gdx/video/VideoPlayerIos.java
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,9 @@ public class VideoPlayerIos extends AbstractVideoPlayer implements VideoPlayer {
AVPlayerItem playerItem;
AVPlayer player;
private volatile boolean playerIsReady;
private volatile boolean isLoaded;
private boolean isLoaded;
private boolean pauseRequested;
private boolean isPlaying;
private volatile boolean isPlaying;
private boolean isLooping;

/* --- Video --- */
Expand All @@ -90,7 +90,7 @@ protected AVPlayerItemVideoOutput createVideoOutput () {
return new AVPlayerItemVideoOutput(attributes);
}

protected void onVideoTrackLoaded (AVAssetTrack track) {
protected synchronized void onVideoTrackLoaded (AVAssetTrack track) {
List<? extends NativeObject> formatDescriptions = track.getFormatDescriptions();
videoTrack = track;
videoFormat = formatDescriptions.get(0).as(CMVideoFormatDescription.class);
Expand All @@ -102,7 +102,14 @@ protected void onVideoTrackLoaded (AVAssetTrack track) {
message += CMVideoCodecType.valueOf(videoFormat.getMediaSubType());
message += " @ " + getVideoWidth() + "x" + getVideoHeight();
Gdx.app.debug("VideoPlayer", message);
if (playerIsReady) setLoaded();
if (playerIsReady) {
Gdx.app.postRunnable(new Runnable() {
@Override
public void run () {
setLoaded();
}
});
}
}

protected void onAudioTrackLoaded (AVAssetTrack track) {
Expand All @@ -115,10 +122,10 @@ protected void onAudioTrackLoaded (AVAssetTrack track) {
Gdx.app.debug("VideoPlayer", message);
}

private void checkReady () {
if (playerIsReady) return;
private synchronized void checkReady () {
if (player == null || player.getStatus() != AVPlayerStatus.ReadyToPlay) return;
playerIsReady = true;
Gdx.app.debug("VideoPlayer", "Video " + file.path() + " is now ready to play!");
// Start prerolling the video player.
// From testing, this isn't guaranteed to finish, so we start playback once
// the player is ready and the video dimensions are available.
Expand All @@ -131,6 +138,7 @@ public void invoke (boolean success) {
}

private void setLoaded () {
if (isLoaded) return;
isLoaded = true;
if (!pauseRequested) {
resume();
Expand Down Expand Up @@ -173,19 +181,23 @@ public void invoke (NSArray<?> nsObjects, NSError nsError) {
player.addKeyValueObserver("status", new NSObject.NSKeyValueObserver() {
@Override
public void observeValue (String keyPath, NSObject object, NSKeyValueChangeInfo change) {
AVPlayer player = object.as(AVPlayer.class);
String message = "Player status is now " + player.getStatus() + ".";
Gdx.app.debug("VideoPlayer", message);
checkReady();
Gdx.app.postRunnable(new Runnable() {
@Override
public void run () {
if (!playerIsReady) checkReady();
}
});
}
});

AVPlayerItem.Notifications.observeDidPlayToEndTime(playerItem, new VoidBlock1<AVPlayerItem>() {
@Override
public void invoke (AVPlayerItem avPlayerItem) {
if (isLooping) {
player.seekToTime(CMTime.Zero());
player.play();
if (isLooping && isPlaying) {
synchronized (VideoPlayerIos.this) {
player.seekToTime(CMTime.Zero());
player.play();
}
} else if (completionListener != null) {
completionListener.onCompletionListener(VideoPlayerIos.this.file);
}
Expand Down Expand Up @@ -215,7 +227,7 @@ protected void updateTextureFromBuffer (CVImageBuffer buffer) {
}

@Override
public boolean update () {
public synchronized boolean update () {
if (player == null) return false;
CMTime position = player.getCurrentTime();
if (!videoOutput.hasNewPixelBufferForItemTime(position)) return false;
Expand All @@ -234,12 +246,12 @@ public Texture getTexture () {

@Override
public boolean isBuffered () {
checkReady();
if (!playerIsReady) checkReady();
return isLoaded;
}

@Override
public void pause () {
public synchronized void pause () {
if (!isLoaded) {
pauseRequested = true;
} else {
Expand All @@ -249,7 +261,7 @@ public void pause () {
}

@Override
public void resume () {
public synchronized void resume () {
pauseRequested = false;
if (isLoaded) {
player.play();
Expand All @@ -258,13 +270,14 @@ public void resume () {
}

@Override
public void stop () {
public synchronized void stop () {
if (player != null) {
player.cancelPendingPrerolls();
pause();
playerItem.removeOutput(videoOutput);
}

isPlaying = false;
playerIsReady = false;
isLoaded = false;
pauseRequested = false;
Expand Down Expand Up @@ -311,7 +324,7 @@ public boolean isPlaying () {
}

@Override
public int getCurrentTimestamp () {
public synchronized int getCurrentTimestamp () {
if (player == null) return 0;
return (int)(player.getCurrentTime().getSeconds() * 1000);
}
Expand All @@ -326,13 +339,13 @@ public void dispose () {
}

@Override
public void setVolume (float volume) {
public synchronized void setVolume (float volume) {
if (player == null) return;
player.setVolume(volume);
}

@Override
public float getVolume () {
public synchronized float getVolume () {
if (player == null) return 0f;
return player.getVolume();
}
Expand Down
8 changes: 6 additions & 2 deletions gdx-video/src/com/badlogic/gdx/video/VideoPlayer.java
Original file line number Diff line number Diff line change
Expand Up @@ -72,18 +72,22 @@ interface CompletionListener {

/** This will set a listener for whenever the video size of a file is known (after calling play). This is needed since the size
* of the video is not directly known after using the play method.
* <p>
* This may be called on any thread, so it is not safe to call other VideoPlayer functions directly.
*
* @param listener The listener to set */
void setOnVideoSizeListener (VideoSizeListener listener);

/** This will set a listener for when the video is done playing. The listener will be called every time a video is done
* playing.
* <p>
* This may be called on any thread, so it is not safe to call other VideoPlayer functions directly.
*
* @param listener The listener to set */
void setOnCompletionListener (CompletionListener listener);

/** This will return the width of the currently playing video.
*
* <p>
* This function cannot be called until the {@link VideoSizeListener} has been called for the currently playing video. If this
* callback has not been set, a good alternative is to wait until the {@link #isBuffered} function returns true, which
* guarantees the availability of the videoSize.
Expand All @@ -92,7 +96,7 @@ interface CompletionListener {
int getVideoWidth ();

/** This will return the height of the currently playing video.
*
* <p>
* This function cannot be called until the {@link VideoSizeListener} has been called for the currently playing video. If this
* callback has not been set, a good alternative is to wait until the {@link #isBuffered} function returns true, which
* guarantees the availability of the videoSize.
Expand Down

0 comments on commit 62506d1

Please sign in to comment.