Commit b35648ea by olly Committed by Oliver Woodman

Add window based methods to ExoPlayer

- This change also enables seeking in live windows in the
  ExoPlayer demo app.
- The added playlist doesn't transition properly by itself,
  but for manual transitions it works correctly, and
  demonstrates seeking into a default position.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=130515880
parent 76bb1042
...@@ -281,7 +281,7 @@ public class PlayerActivity extends Activity implements OnKeyListener, OnTouchLi ...@@ -281,7 +281,7 @@ public class PlayerActivity extends Activity implements OnKeyListener, OnTouchLi
player.setVideoListener(this); player.setVideoListener(this);
player.setVideoSurfaceHolder(surfaceView.getHolder()); player.setVideoSurfaceHolder(surfaceView.getHolder());
if (shouldRestorePosition) { if (shouldRestorePosition) {
player.seekTo(playerPeriodIndex, playerPosition); player.seekInPeriod(playerPeriodIndex, playerPosition);
} }
player.setPlayWhenReady(true); player.setPlayWhenReady(true);
mediaController.setMediaPlayer(new PlayerControl(player)); mediaController.setMediaPlayer(new PlayerControl(player));
...@@ -371,7 +371,7 @@ public class PlayerActivity extends Activity implements OnKeyListener, OnTouchLi ...@@ -371,7 +371,7 @@ public class PlayerActivity extends Activity implements OnKeyListener, OnTouchLi
debugViewHelper.stop(); debugViewHelper.stop();
debugViewHelper = null; debugViewHelper = null;
playerPeriodIndex = player.getCurrentPeriodIndex(); playerPeriodIndex = player.getCurrentPeriodIndex();
playerPosition = player.getCurrentPosition(); playerPosition = player.getCurrentPositionInPeriod();
shouldRestorePosition = false; shouldRestorePosition = false;
Timeline playerTimeline = player.getCurrentTimeline(); Timeline playerTimeline = player.getCurrentTimeline();
if (playerTimeline != null) { if (playerTimeline != null) {
......
...@@ -245,7 +245,7 @@ public interface ExoPlayer { ...@@ -245,7 +245,7 @@ public interface ExoPlayer {
* @param mediaSource The {@link MediaSource} to play. * @param mediaSource The {@link MediaSource} to play.
* @param resetPosition Whether the playback position should be reset to the source's default * @param resetPosition Whether the playback position should be reset to the source's default
* position. If false, playback will start from the position defined by * position. If false, playback will start from the position defined by
* {@link #getCurrentPeriodIndex()} and {@link #getCurrentPosition()}. * {@link #getCurrentPeriodIndex()} and {@link #getCurrentPositionInPeriod()}.
*/ */
void setMediaSource(MediaSource mediaSource, boolean resetPosition); void setMediaSource(MediaSource mediaSource, boolean resetPosition);
...@@ -278,26 +278,52 @@ public interface ExoPlayer { ...@@ -278,26 +278,52 @@ public interface ExoPlayer {
* *
* @param positionMs The seek position. * @param positionMs The seek position.
*/ */
void seekTo(long positionMs); void seekInCurrentPeriod(long positionMs);
/**
* Seeks to the default position associated with the specified period. The position can depend on
* the type of source passed to {@link #setMediaSource(MediaSource)}. For live streams it will
* typically be the live edge of the window to which the period belongs. For other streams it will
* typically be the start of the period.
*
* @param periodIndex The index of the period whose associated default position should be seeked
* to.
*/
void seekToDefaultPositionForPeriod(int periodIndex);
/** /**
* Seeks to a position specified in milliseconds in the specified period. * Seeks to a position specified in milliseconds in the specified period.
* *
* @param periodIndex The index of the period to seek to. * @param periodIndex The index of the period.
* @param positionMs The seek position relative to the start of the specified period. * @param positionMs The seek position relative to the start of the period.
*/ */
void seekTo(int periodIndex, long positionMs); void seekInPeriod(int periodIndex, long positionMs);
/** /**
* Seeks to the default position associated with the specified period. The position can depend on * Seeks to a position specified in milliseconds in the current window.
*
* @param positionMs The seek position.
*/
void seekInCurrentWindow(long positionMs);
/**
* Seeks to the default position associated with the specified window. The position can depend on
* the type of source passed to {@link #setMediaSource(MediaSource)}. For live streams it will * the type of source passed to {@link #setMediaSource(MediaSource)}. For live streams it will
* typically be the live edge. For other types of streams it will typically be the start of the * typically be the live edge of the window. For other streams it will typically be the start of
* stream. * the window.
* *
* @param periodIndex The index of the period whose associated default position should be seeked * @param windowIndex The index of the window whose associated default position should be seeked
* to. * to.
*/ */
void seekToDefaultPosition(int periodIndex); void seekToDefaultPositionForWindow(int windowIndex);
/**
* Seeks to a position specified in milliseconds in the specified seek window.
*
* @param seekWindowIndex The index of the seek window.
* @param positionMs The seek position relative to the start of the window.
*/
void seekInWindow(int seekWindowIndex, long positionMs);
/** /**
* Stops playback. Use {@code setPlayWhenReady(false)} rather than this method if the intention * Stops playback. Use {@code setPlayWhenReady(false)} rather than this method if the intention
...@@ -335,42 +361,81 @@ public interface ExoPlayer { ...@@ -335,42 +361,81 @@ public interface ExoPlayer {
void blockingSendMessages(ExoPlayerMessage... messages); void blockingSendMessages(ExoPlayerMessage... messages);
/** /**
* Returns the current {@link Timeline}, or {@code null} if there is no timeline.
*/
Timeline getCurrentTimeline();
/**
* Returns the current manifest. The type depends on the {@link MediaSource} passed to
* {@link #setMediaSource(MediaSource)} or {@link #setMediaSource(MediaSource, boolean)}.
*/
Object getCurrentManifest();
// Period based.
/**
* Returns the index of the current period.
*/
int getCurrentPeriodIndex();
/**
* Returns the duration of the current period in milliseconds, or {@link #UNKNOWN_TIME} if the * Returns the duration of the current period in milliseconds, or {@link #UNKNOWN_TIME} if the
* duration is not known. * duration is not known.
*/ */
long getDuration(); long getCurrentPeriodDuration();
/** /**
* Returns the playback position in the current period, in milliseconds. * Returns the playback position in the current period, in milliseconds.
*/ */
long getCurrentPosition(); long getCurrentPositionInPeriod();
/** /**
* Returns the index of the current period. * Returns an estimate of the position in the current period up to which data is buffered, or
* {@link #UNKNOWN_TIME} if no estimate is available.
*/ */
int getCurrentPeriodIndex(); long getBufferedPositionInPeriod();
/** /**
* Returns the current {@link Timeline}, or {@code null} if there is no timeline. * Returns an estimate of the percentage in the current period up to which data is buffered, or 0
* if no estimate is available.
*/ */
Timeline getCurrentTimeline(); int getBufferedPercentageInPeriod();
// Window based.
/** /**
* Returns the current manifest. The type depends on the {@link MediaSource} passed to * Returns the index of the seek window associated with the current period, or -1 if the timeline
* {@link #setMediaSource(MediaSource)} or {@link #setMediaSource(MediaSource, boolean)}. * is not set.
*/ */
Object getCurrentManifest(); int getCurrentWindowIndex();
/** /**
* Returns an estimate of the absolute position in milliseconds up to which data is buffered, * Returns the duration of the current window in milliseconds, or {@link #UNKNOWN_TIME} if the
* or {@link #UNKNOWN_TIME} if no estimate is available. * duration is not known.
*/ */
long getBufferedPosition(); long getCurrentWindowDuration();
/** /**
* Returns an estimate of the percentage into the media up to which data is buffered, or 0 if no * Returns the playback position in the current seek window, in milliseconds, or
* estimate is available. * {@link #UNKNOWN_TIME} if the timeline is not set.
*/ */
int getBufferedPercentage(); long getCurrentPositionInWindow();
/**
* Returns an estimate of the position in the current window up to which data is buffered, or
* {@link #UNKNOWN_TIME} if no estimate is available.
*/
long getBufferedPositionInWindow();
/**
* Returns an estimate of the percentage in the current window up to which data is buffered, or 0
* if no estimate is available.
*/
int getBufferedPercentageInWindow();
// Misc methods
// TODO - Add a method/methods to expose this.
// getBufferedPosition -> periodIndex,position
} }
...@@ -127,12 +127,17 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -127,12 +127,17 @@ import java.util.concurrent.CopyOnWriteArraySet;
} }
@Override @Override
public void seekTo(long positionMs) { public void seekInCurrentPeriod(long positionMs) {
seekTo(getCurrentPeriodIndex(), positionMs); seekInPeriod(getCurrentPeriodIndex(), positionMs);
} }
@Override @Override
public void seekTo(int periodIndex, long positionMs) { public void seekToDefaultPositionForPeriod(int periodIndex) {
seekInPeriod(periodIndex, UNKNOWN_TIME);
}
@Override
public void seekInPeriod(int periodIndex, long positionMs) {
boolean seekToDefaultPosition = positionMs == UNKNOWN_TIME; boolean seekToDefaultPosition = positionMs == UNKNOWN_TIME;
maskingPeriodIndex = periodIndex; maskingPeriodIndex = periodIndex;
maskingPositionMs = seekToDefaultPosition ? 0 : positionMs; maskingPositionMs = seekToDefaultPosition ? 0 : positionMs;
...@@ -146,8 +151,41 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -146,8 +151,41 @@ import java.util.concurrent.CopyOnWriteArraySet;
} }
@Override @Override
public void seekToDefaultPosition(int periodIndex) { public void seekInCurrentWindow(long positionMs) {
seekTo(periodIndex, UNKNOWN_TIME); Timeline timeline = getCurrentTimeline();
if (timeline == null) {
throw new IllegalArgumentException("Windows are not yet known");
}
int windowIndex = timeline.getPeriodWindowIndex(getCurrentPeriodIndex());
seekInWindow(windowIndex, positionMs);
}
@Override
public void seekToDefaultPositionForWindow(int windowIndex) {
if (timeline == null) {
throw new IllegalArgumentException("Windows are not yet known");
}
Assertions.checkIndex(windowIndex, 0, timeline.getWindowCount());
Window window = timeline.getWindow(windowIndex);
seekToDefaultPositionForPeriod(window.startPeriodIndex);
}
@Override
public void seekInWindow(int windowIndex, long positionMs) {
if (timeline == null) {
throw new IllegalArgumentException("Windows are not yet known");
}
Assertions.checkIndex(windowIndex, 0, timeline.getWindowCount());
Window window = timeline.getWindow(windowIndex);
int periodIndex = window.startPeriodIndex;
long periodPositionMs = window.startTimeMs + positionMs;
long periodDurationMs = timeline.getPeriodDurationMs(periodIndex);
while (periodDurationMs != UNKNOWN_TIME && periodPositionMs >= periodDurationMs
&& periodIndex < window.endPeriodIndex) {
periodPositionMs -= periodDurationMs;
periodDurationMs = timeline.getPeriodDurationMs(++periodIndex);
}
seekInPeriod(periodIndex, periodPositionMs);
} }
@Override @Override
...@@ -172,7 +210,12 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -172,7 +210,12 @@ import java.util.concurrent.CopyOnWriteArraySet;
} }
@Override @Override
public long getDuration() { public int getCurrentPeriodIndex() {
return pendingSeekAcks == 0 ? playbackInfo.periodIndex : maskingPeriodIndex;
}
@Override
public long getCurrentPeriodDuration() {
if (timeline == null) { if (timeline == null) {
return UNKNOWN_TIME; return UNKNOWN_TIME;
} }
...@@ -180,44 +223,101 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -180,44 +223,101 @@ import java.util.concurrent.CopyOnWriteArraySet;
} }
@Override @Override
public long getCurrentPosition() { public long getCurrentPositionInPeriod() {
return pendingSeekAcks > 0 ? maskingPositionMs return pendingSeekAcks > 0 ? maskingPositionMs
: playbackInfo.positionUs == C.UNSET_TIME_US ? 0 : (playbackInfo.positionUs / 1000); : playbackInfo.positionUs == C.UNSET_TIME_US ? 0 : (playbackInfo.positionUs / 1000);
} }
@Override @Override
public int getCurrentPeriodIndex() { public long getBufferedPositionInPeriod() {
return pendingSeekAcks == 0 ? playbackInfo.periodIndex : maskingPeriodIndex; if (pendingSeekAcks == 0) {
long bufferedPositionUs = playbackInfo.bufferedPositionUs;
return bufferedPositionUs == C.UNSET_TIME_US ? UNKNOWN_TIME : (bufferedPositionUs / 1000);
} else {
return maskingPositionMs;
}
} }
@Override @Override
public Timeline getCurrentTimeline() { public int getBufferedPercentageInPeriod() {
return timeline; if (timeline == null) {
return 0;
}
long bufferedPosition = getBufferedPositionInPeriod();
long duration = getCurrentPeriodDuration();
return bufferedPosition == ExoPlayer.UNKNOWN_TIME || duration == ExoPlayer.UNKNOWN_TIME ? 0
: (int) (duration == 0 ? 100 : (bufferedPosition * 100) / duration);
} }
@Override @Override
public Object getCurrentManifest() { public int getCurrentWindowIndex() {
return manifest; if (timeline == null) {
return -1;
}
return timeline.getPeriodWindowIndex(getCurrentPeriodIndex());
} }
@Override @Override
public long getBufferedPosition() { public long getCurrentWindowDuration() {
if (pendingSeekAcks == 0) { if (timeline == null) {
long bufferedPositionUs = playbackInfo.bufferedPositionUs; return UNKNOWN_TIME;
return bufferedPositionUs == C.UNSET_TIME_US ? UNKNOWN_TIME : (bufferedPositionUs / 1000);
} else {
return maskingPositionMs;
} }
return timeline.getWindow(getCurrentWindowIndex()).durationMs;
} }
@Override @Override
public int getBufferedPercentage() { public long getCurrentPositionInWindow() {
long bufferedPosition = getBufferedPosition(); if (timeline == null) {
long duration = getDuration(); return UNKNOWN_TIME;
return bufferedPosition == UNKNOWN_TIME || duration == UNKNOWN_TIME ? 0 }
int periodIndex = getCurrentPeriodIndex();
int windowIndex = timeline.getPeriodWindowIndex(periodIndex);
Window window = timeline.getWindow(windowIndex);
long position = getCurrentPositionInPeriod();
for (int i = window.startPeriodIndex; i < periodIndex; i++) {
position += timeline.getPeriodDurationMs(i);
}
position -= window.startTimeMs;
return position;
}
@Override
public long getBufferedPositionInWindow() {
// TODO - Implement this properly.
if (timeline == null) {
return UNKNOWN_TIME;
}
int periodIndex = getCurrentPeriodIndex();
int windowIndex = timeline.getPeriodWindowIndex(periodIndex);
Window window = timeline.getWindow(windowIndex);
if (window.startPeriodIndex == periodIndex && window.endPeriodIndex == periodIndex
&& window.startTimeMs == 0 && window.durationMs == getCurrentPeriodDuration()) {
return getBufferedPositionInPeriod();
}
return getCurrentPositionInWindow();
}
@Override
public int getBufferedPercentageInWindow() {
if (timeline == null) {
return 0;
}
long bufferedPosition = getBufferedPositionInWindow();
long duration = getCurrentWindowDuration();
return bufferedPosition == ExoPlayer.UNKNOWN_TIME || duration == ExoPlayer.UNKNOWN_TIME ? 0
: (int) (duration == 0 ? 100 : (bufferedPosition * 100) / duration); : (int) (duration == 0 ? 100 : (bufferedPosition * 100) / duration);
} }
@Override
public Timeline getCurrentTimeline() {
return timeline;
}
@Override
public Object getCurrentManifest() {
return manifest;
}
// Not private so it can be called from an inner class without going through a thunk method. // Not private so it can be called from an inner class without going through a thunk method.
/* package */ void handleEvent(Message msg) { /* package */ void handleEvent(Message msg) {
switch (msg.what) { switch (msg.what) {
......
...@@ -344,18 +344,33 @@ public final class SimpleExoPlayer implements ExoPlayer { ...@@ -344,18 +344,33 @@ public final class SimpleExoPlayer implements ExoPlayer {
} }
@Override @Override
public void seekTo(long positionMs) { public void seekInCurrentPeriod(long positionMs) {
player.seekTo(positionMs); player.seekInCurrentPeriod(positionMs);
} }
@Override @Override
public void seekTo(int periodIndex, long positionMs) { public void seekToDefaultPositionForPeriod(int periodIndex) {
player.seekTo(periodIndex, positionMs); player.seekToDefaultPositionForPeriod(periodIndex);
} }
@Override @Override
public void seekToDefaultPosition(int periodIndex) { public void seekInPeriod(int periodIndex, long positionMs) {
player.seekToDefaultPosition(periodIndex); player.seekInPeriod(periodIndex, positionMs);
}
@Override
public void seekInCurrentWindow(long positionMs) {
player.seekInCurrentWindow(positionMs);
}
@Override
public void seekToDefaultPositionForWindow(int windowIndex) {
player.seekToDefaultPositionForWindow(windowIndex);
}
@Override
public void seekInWindow(int windowIndex, long positionMs) {
player.seekInWindow(windowIndex, positionMs);
} }
@Override @Override
...@@ -379,38 +394,63 @@ public final class SimpleExoPlayer implements ExoPlayer { ...@@ -379,38 +394,63 @@ public final class SimpleExoPlayer implements ExoPlayer {
} }
@Override @Override
public long getDuration() { public int getCurrentPeriodIndex() {
return player.getDuration(); return player.getCurrentPeriodIndex();
} }
@Override @Override
public long getCurrentPosition() { public long getCurrentPeriodDuration() {
return player.getCurrentPosition(); return player.getCurrentPeriodDuration();
} }
@Override @Override
public int getCurrentPeriodIndex() { public long getCurrentPositionInPeriod() {
return player.getCurrentPeriodIndex(); return player.getCurrentPositionInPeriod();
} }
@Override @Override
public Timeline getCurrentTimeline() { public long getBufferedPositionInPeriod() {
return player.getCurrentTimeline(); return player.getBufferedPositionInPeriod();
} }
@Override @Override
public Object getCurrentManifest() { public int getBufferedPercentageInPeriod() {
return player.getCurrentManifest(); return player.getBufferedPercentageInPeriod();
}
@Override
public int getCurrentWindowIndex() {
return player.getCurrentWindowIndex();
}
@Override
public long getCurrentWindowDuration() {
return player.getCurrentWindowDuration();
}
@Override
public long getCurrentPositionInWindow() {
return player.getCurrentPositionInWindow();
}
@Override
public long getBufferedPositionInWindow() {
return player.getBufferedPositionInWindow();
} }
@Override @Override
public long getBufferedPosition() { public int getBufferedPercentageInWindow() {
return player.getBufferedPosition(); return player.getBufferedPercentageInWindow();
} }
@Override @Override
public int getBufferedPercentage() { public Timeline getCurrentTimeline() {
return player.getBufferedPercentage(); return player.getCurrentTimeline();
}
@Override
public Object getCurrentManifest() {
return player.getCurrentManifest();
} }
// Internal methods. // Internal methods.
......
...@@ -48,17 +48,20 @@ public class MediaControllerPrevNextClickListener implements OnClickListener { ...@@ -48,17 +48,20 @@ public class MediaControllerPrevNextClickListener implements OnClickListener {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
int currentPeriodIndex = player.getCurrentPeriodIndex(); int currentWindowIndex = player.getCurrentWindowIndex();
if (currentWindowIndex == -1) {
return;
}
if (isNext) { if (isNext) {
if (currentPeriodIndex < player.getCurrentTimeline().getPeriodCount() - 1) { if (currentWindowIndex < player.getCurrentTimeline().getWindowCount() - 1) {
player.seekTo(currentPeriodIndex + 1, 0); player.seekToDefaultPositionForWindow(currentWindowIndex + 1);
} }
} else { } else {
if (currentPeriodIndex > 0 if (currentWindowIndex > 0
&& player.getCurrentPosition() <= MAX_POSITION_FOR_SEEK_TO_PREVIOUS_PERIOD) { && player.getCurrentPositionInWindow() <= MAX_POSITION_FOR_SEEK_TO_PREVIOUS_PERIOD) {
player.seekTo(currentPeriodIndex - 1, 0); player.seekToDefaultPositionForWindow(currentWindowIndex - 1);
} else { } else {
player.seekTo(currentPeriodIndex, 0); player.seekInWindow(currentWindowIndex, 0);
} }
} }
} }
......
...@@ -18,7 +18,6 @@ package com.google.android.exoplayer2.ui; ...@@ -18,7 +18,6 @@ package com.google.android.exoplayer2.ui;
import android.widget.MediaController.MediaPlayerControl; import android.widget.MediaController.MediaPlayerControl;
import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.audio.MediaCodecAudioRenderer; import com.google.android.exoplayer2.audio.MediaCodecAudioRenderer;
import com.google.android.exoplayer2.util.Util;
/** /**
* An implementation of {@link MediaPlayerControl} for controlling an {@link ExoPlayer} instance. * An implementation of {@link MediaPlayerControl} for controlling an {@link ExoPlayer} instance.
...@@ -65,19 +64,19 @@ public class PlayerControl implements MediaPlayerControl { ...@@ -65,19 +64,19 @@ public class PlayerControl implements MediaPlayerControl {
@Override @Override
public int getBufferPercentage() { public int getBufferPercentage() {
return exoPlayer.getBufferedPercentage(); return exoPlayer.getBufferedPercentageInWindow();
} }
@Override @Override
public int getCurrentPosition() { public int getCurrentPosition() {
return exoPlayer.getDuration() == ExoPlayer.UNKNOWN_TIME ? 0 long position = exoPlayer.getCurrentPositionInWindow();
: (int) exoPlayer.getCurrentPosition(); return position == ExoPlayer.UNKNOWN_TIME ? 0 : (int) position;
} }
@Override @Override
public int getDuration() { public int getDuration() {
return exoPlayer.getDuration() == ExoPlayer.UNKNOWN_TIME ? 0 long duration = exoPlayer.getCurrentWindowDuration();
: (int) exoPlayer.getDuration(); return duration == ExoPlayer.UNKNOWN_TIME ? 0 : (int) duration;
} }
@Override @Override
...@@ -97,9 +96,11 @@ public class PlayerControl implements MediaPlayerControl { ...@@ -97,9 +96,11 @@ public class PlayerControl implements MediaPlayerControl {
@Override @Override
public void seekTo(int timeMillis) { public void seekTo(int timeMillis) {
long seekPosition = exoPlayer.getDuration() == ExoPlayer.UNKNOWN_TIME ? 0 int windowIndex = exoPlayer.getCurrentWindowIndex();
: Util.constrainValue(timeMillis, 0, getDuration()); if (windowIndex == -1) {
exoPlayer.seekTo(seekPosition); return;
}
exoPlayer.seekInWindow(windowIndex, timeMillis);
} }
} }
...@@ -56,7 +56,7 @@ public abstract class Action { ...@@ -56,7 +56,7 @@ public abstract class Action {
protected abstract void doActionImpl(ExoPlayer player, MappingTrackSelector trackSelector); protected abstract void doActionImpl(ExoPlayer player, MappingTrackSelector trackSelector);
/** /**
* Calls {@link ExoPlayer#seekTo(long)}. * Calls {@link ExoPlayer#seekInCurrentPeriod(long)}.
*/ */
public static final class Seek extends Action { public static final class Seek extends Action {
...@@ -73,7 +73,7 @@ public abstract class Action { ...@@ -73,7 +73,7 @@ public abstract class Action {
@Override @Override
protected void doActionImpl(ExoPlayer player, MappingTrackSelector trackSelector) { protected void doActionImpl(ExoPlayer player, MappingTrackSelector trackSelector) {
player.seekTo(positionMs); player.seekInCurrentPeriod(positionMs);
} }
} }
......
...@@ -150,7 +150,7 @@ public abstract class ExoHostedTest implements HostedTest, ExoPlayer.EventListen ...@@ -150,7 +150,7 @@ public abstract class ExoHostedTest implements HostedTest, ExoPlayer.EventListen
@Override @Override
public final void onStop() { public final void onStop() {
actionHandler.removeCallbacksAndMessages(null); actionHandler.removeCallbacksAndMessages(null);
sourceDurationMs = player.getDuration(); sourceDurationMs = player.getCurrentPeriodDuration();
player.release(); player.release();
player = null; player = null;
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment