mirror of
https://github.com/samsonjs/media.git
synced 2026-03-26 09:35:47 +00:00
This is required for buffering to work properly across playlist transitions. It's also much simpler, since specifying the buffering policy becomes independent of the type of media being played (i.e. the source). ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=126051755
278 lines
8.4 KiB
Java
278 lines
8.4 KiB
Java
/*
|
|
* Copyright (C) 2014 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
package com.google.android.exoplayer;
|
|
|
|
import com.google.android.exoplayer.ExoPlayerImplInternal.PlaybackInfo;
|
|
import com.google.android.exoplayer.util.Assertions;
|
|
|
|
import android.annotation.SuppressLint;
|
|
import android.os.Handler;
|
|
import android.os.Looper;
|
|
import android.os.Message;
|
|
import android.util.Log;
|
|
|
|
import java.util.concurrent.CopyOnWriteArraySet;
|
|
|
|
/**
|
|
* Concrete implementation of {@link ExoPlayer}.
|
|
*/
|
|
/* package */ final class ExoPlayerImpl implements ExoPlayer {
|
|
|
|
private static final String TAG = "ExoPlayerImpl";
|
|
|
|
private final Handler eventHandler;
|
|
private final ExoPlayerImplInternal internalPlayer;
|
|
private final CopyOnWriteArraySet<EventListener> listeners;
|
|
|
|
private boolean playWhenReady;
|
|
private int playbackState;
|
|
private int pendingPlayWhenReadyAcks;
|
|
private int pendingSetSourceProviderAndSeekAcks;
|
|
|
|
// Playback information when there is no pending seek/set source operation.
|
|
private PlaybackInfo playbackInfo;
|
|
|
|
// Playback information when there is a pending seek/set source operation.
|
|
private int maskingSourceIndex;
|
|
private long maskingPositionMs;
|
|
private long maskingDurationMs;
|
|
|
|
/**
|
|
* Constructs an instance. Must be invoked from a thread that has an associated {@link Looper}.
|
|
*
|
|
* @param renderers The {@link TrackRenderer}s that will be used by the instance.
|
|
* @param trackSelector The {@link TrackSelector} that will be used by the instance.
|
|
* @param bufferingPolicy The {@link BufferingPolicy} that will be used by the instance.
|
|
*/
|
|
@SuppressLint("HandlerLeak")
|
|
public ExoPlayerImpl(TrackRenderer[] renderers, TrackSelector trackSelector,
|
|
BufferingPolicy bufferingPolicy) {
|
|
Log.i(TAG, "Init " + ExoPlayerLibraryInfo.VERSION);
|
|
Assertions.checkNotNull(renderers);
|
|
Assertions.checkState(renderers.length > 0);
|
|
this.playWhenReady = false;
|
|
this.playbackState = STATE_IDLE;
|
|
this.listeners = new CopyOnWriteArraySet<>();
|
|
eventHandler = new Handler() {
|
|
@Override
|
|
public void handleMessage(Message msg) {
|
|
ExoPlayerImpl.this.handleEvent(msg);
|
|
}
|
|
};
|
|
internalPlayer = new ExoPlayerImplInternal(renderers, trackSelector, bufferingPolicy,
|
|
playWhenReady, eventHandler);
|
|
playbackInfo = new ExoPlayerImplInternal.PlaybackInfo(0);
|
|
}
|
|
|
|
@Override
|
|
public void addListener(EventListener listener) {
|
|
listeners.add(listener);
|
|
}
|
|
|
|
@Override
|
|
public void removeListener(EventListener listener) {
|
|
listeners.remove(listener);
|
|
}
|
|
|
|
@Override
|
|
public int getPlaybackState() {
|
|
return playbackState;
|
|
}
|
|
|
|
@Override
|
|
public void setSource(final SampleSource sampleSource) {
|
|
setSourceProvider(new SingleSampleSourceProvider(sampleSource));
|
|
}
|
|
|
|
@Override
|
|
public void setSourceProvider(SampleSourceProvider sourceProvider) {
|
|
maskingSourceIndex = 0;
|
|
maskingPositionMs = 0;
|
|
maskingDurationMs = ExoPlayer.UNKNOWN_TIME;
|
|
|
|
pendingSetSourceProviderAndSeekAcks++;
|
|
internalPlayer.setSourceProvider(sourceProvider);
|
|
for (EventListener listener : listeners) {
|
|
listener.onPositionDiscontinuity(0, 0);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void setPlayWhenReady(boolean playWhenReady) {
|
|
if (this.playWhenReady != playWhenReady) {
|
|
this.playWhenReady = playWhenReady;
|
|
pendingPlayWhenReadyAcks++;
|
|
internalPlayer.setPlayWhenReady(playWhenReady);
|
|
for (EventListener listener : listeners) {
|
|
listener.onPlayerStateChanged(playWhenReady, playbackState);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean getPlayWhenReady() {
|
|
return playWhenReady;
|
|
}
|
|
|
|
@Override
|
|
public boolean isPlayWhenReadyCommitted() {
|
|
return pendingPlayWhenReadyAcks == 0;
|
|
}
|
|
|
|
@Override
|
|
public void seekTo(long positionMs) {
|
|
seekTo(getCurrentSourceIndex(), positionMs);
|
|
}
|
|
|
|
@Override
|
|
public void seekTo(int sourceIndex, long positionMs) {
|
|
boolean sourceChanging = sourceIndex != getCurrentSourceIndex();
|
|
maskingSourceIndex = sourceIndex;
|
|
maskingPositionMs = positionMs;
|
|
maskingDurationMs = sourceChanging ? ExoPlayer.UNKNOWN_TIME : getDuration();
|
|
|
|
pendingSetSourceProviderAndSeekAcks++;
|
|
internalPlayer.seekTo(sourceIndex, positionMs * 1000);
|
|
for (EventListener listener : listeners) {
|
|
listener.onPositionDiscontinuity(sourceIndex, positionMs);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void stop() {
|
|
internalPlayer.stop();
|
|
}
|
|
|
|
@Override
|
|
public void release() {
|
|
internalPlayer.release();
|
|
eventHandler.removeCallbacksAndMessages(null);
|
|
}
|
|
|
|
@Override
|
|
public void sendMessages(ExoPlayerMessage... messages) {
|
|
internalPlayer.sendMessages(messages);
|
|
}
|
|
|
|
@Override
|
|
public void blockingSendMessages(ExoPlayerMessage... messages) {
|
|
internalPlayer.blockingSendMessages(messages);
|
|
}
|
|
|
|
@Override
|
|
public long getDuration() {
|
|
if (pendingSetSourceProviderAndSeekAcks == 0) {
|
|
long durationUs = playbackInfo.durationUs;
|
|
return durationUs == C.UNSET_TIME_US ? ExoPlayer.UNKNOWN_TIME : durationUs / 1000;
|
|
} else {
|
|
return maskingDurationMs;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public long getCurrentPosition() {
|
|
return pendingSetSourceProviderAndSeekAcks == 0 ? playbackInfo.positionUs / 1000
|
|
: maskingPositionMs;
|
|
}
|
|
|
|
@Override
|
|
public int getCurrentSourceIndex() {
|
|
return pendingSetSourceProviderAndSeekAcks == 0 ? playbackInfo.sourceIndex : maskingSourceIndex;
|
|
}
|
|
|
|
@Override
|
|
public long getBufferedPosition() {
|
|
if (pendingSetSourceProviderAndSeekAcks == 0) {
|
|
long bufferedPositionUs = playbackInfo.bufferedPositionUs;
|
|
return bufferedPositionUs == C.END_OF_SOURCE_US ? getDuration() : bufferedPositionUs / 1000;
|
|
} else {
|
|
return maskingPositionMs;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public int getBufferedPercentage() {
|
|
long bufferedPosition = getBufferedPosition();
|
|
long duration = getDuration();
|
|
return bufferedPosition == ExoPlayer.UNKNOWN_TIME || duration == ExoPlayer.UNKNOWN_TIME ? 0
|
|
: (int) (duration == 0 ? 100 : (bufferedPosition * 100) / duration);
|
|
}
|
|
|
|
// Not private so it can be called from an inner class without going through a thunk method.
|
|
/* package */ void handleEvent(Message msg) {
|
|
switch (msg.what) {
|
|
case ExoPlayerImplInternal.MSG_STATE_CHANGED: {
|
|
playbackState = msg.arg1;
|
|
for (EventListener listener : listeners) {
|
|
listener.onPlayerStateChanged(playWhenReady, playbackState);
|
|
}
|
|
break;
|
|
}
|
|
case ExoPlayerImplInternal.MSG_SET_PLAY_WHEN_READY_ACK: {
|
|
pendingPlayWhenReadyAcks--;
|
|
if (pendingPlayWhenReadyAcks == 0) {
|
|
for (EventListener listener : listeners) {
|
|
listener.onPlayWhenReadyCommitted();
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case ExoPlayerImplInternal.MSG_SET_SOURCE_PROVIDER_ACK: // Fall through.
|
|
case ExoPlayerImplInternal.MSG_SEEK_ACK: {
|
|
pendingSetSourceProviderAndSeekAcks--;
|
|
break;
|
|
}
|
|
case ExoPlayerImplInternal.MSG_SOURCE_CHANGED: {
|
|
playbackInfo = (ExoPlayerImplInternal.PlaybackInfo) msg.obj;
|
|
if (pendingSetSourceProviderAndSeekAcks == 0) {
|
|
for (EventListener listener : listeners) {
|
|
listener.onPositionDiscontinuity(playbackInfo.sourceIndex, 0);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case ExoPlayerImplInternal.MSG_ERROR: {
|
|
ExoPlaybackException exception = (ExoPlaybackException) msg.obj;
|
|
for (EventListener listener : listeners) {
|
|
listener.onPlayerError(exception);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private static final class SingleSampleSourceProvider implements SampleSourceProvider {
|
|
|
|
private final SampleSource sampleSource;
|
|
|
|
public SingleSampleSourceProvider(SampleSource sampleSource) {
|
|
this.sampleSource = sampleSource;
|
|
}
|
|
|
|
@Override
|
|
public int getSourceCount() {
|
|
return 1;
|
|
}
|
|
|
|
@Override
|
|
public SampleSource createSource(int index) {
|
|
// The source will only be created once.
|
|
return sampleSource;
|
|
}
|
|
|
|
}
|
|
|
|
}
|