mirror of
https://github.com/samsonjs/media.git
synced 2026-04-07 11:35:46 +00:00
1. Remove requirement for TrackRenderer implementations to report current position, unless they are time sources. 2. Expose whether renderers have media to play. The immediate benefit of this is to solve the referenced GitHub issue, and also to only display the appropriate Audio/Video/Text buttons in the demo app for the media being played. This is also a natural step toward multi-track support. Github issue: #541
230 lines
6.9 KiB
Java
230 lines
6.9 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 android.annotation.SuppressLint;
|
|
import android.os.Handler;
|
|
import android.os.Looper;
|
|
import android.os.Message;
|
|
import android.util.Log;
|
|
|
|
import java.util.Arrays;
|
|
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<Listener> listeners;
|
|
private final boolean[] rendererHasMediaFlags;
|
|
private final boolean[] rendererEnabledFlags;
|
|
|
|
private boolean playWhenReady;
|
|
private int playbackState;
|
|
private int pendingPlayWhenReadyAcks;
|
|
|
|
/**
|
|
* Constructs an instance. Must be invoked from a thread that has an associated {@link Looper}.
|
|
*
|
|
* @param rendererCount The number of {@link TrackRenderer}s that will be passed to
|
|
* {@link #prepare(TrackRenderer[])}.
|
|
* @param minBufferMs A minimum duration of data that must be buffered for playback to start
|
|
* or resume following a user action such as a seek.
|
|
* @param minRebufferMs A minimum duration of data that must be buffered for playback to resume
|
|
* after a player invoked rebuffer (i.e. a rebuffer that occurs due to buffer depletion, and
|
|
* not due to a user action such as starting playback or seeking).
|
|
*/
|
|
@SuppressLint("HandlerLeak")
|
|
public ExoPlayerImpl(int rendererCount, int minBufferMs, int minRebufferMs) {
|
|
Log.i(TAG, "Init " + ExoPlayerLibraryInfo.VERSION);
|
|
this.playWhenReady = false;
|
|
this.playbackState = STATE_IDLE;
|
|
this.listeners = new CopyOnWriteArraySet<>();
|
|
this.rendererHasMediaFlags = new boolean[rendererCount];
|
|
this.rendererEnabledFlags = new boolean[rendererCount];
|
|
for (int i = 0; i < rendererEnabledFlags.length; i++) {
|
|
rendererEnabledFlags[i] = true;
|
|
}
|
|
eventHandler = new Handler() {
|
|
@Override
|
|
public void handleMessage(Message msg) {
|
|
ExoPlayerImpl.this.handleEvent(msg);
|
|
}
|
|
};
|
|
internalPlayer = new ExoPlayerImplInternal(eventHandler, playWhenReady, rendererEnabledFlags,
|
|
minBufferMs, minRebufferMs);
|
|
}
|
|
|
|
@Override
|
|
public Looper getPlaybackLooper() {
|
|
return internalPlayer.getPlaybackLooper();
|
|
}
|
|
|
|
@Override
|
|
public void addListener(Listener listener) {
|
|
listeners.add(listener);
|
|
}
|
|
|
|
@Override
|
|
public void removeListener(Listener listener) {
|
|
listeners.remove(listener);
|
|
}
|
|
|
|
@Override
|
|
public int getPlaybackState() {
|
|
return playbackState;
|
|
}
|
|
|
|
@Override
|
|
public void prepare(TrackRenderer... renderers) {
|
|
Arrays.fill(rendererHasMediaFlags, false);
|
|
internalPlayer.prepare(renderers);
|
|
}
|
|
|
|
@Override
|
|
public boolean getRendererHasMedia(int rendererIndex) {
|
|
return rendererHasMediaFlags[rendererIndex];
|
|
}
|
|
|
|
@Override
|
|
public void setRendererEnabled(int rendererIndex, boolean enabled) {
|
|
if (rendererEnabledFlags[rendererIndex] != enabled) {
|
|
rendererEnabledFlags[rendererIndex] = enabled;
|
|
internalPlayer.setRendererEnabled(rendererIndex, enabled);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean getRendererEnabled(int rendererIndex) {
|
|
return rendererEnabledFlags[rendererIndex];
|
|
}
|
|
|
|
@Override
|
|
public void setPlayWhenReady(boolean playWhenReady) {
|
|
if (this.playWhenReady != playWhenReady) {
|
|
this.playWhenReady = playWhenReady;
|
|
pendingPlayWhenReadyAcks++;
|
|
internalPlayer.setPlayWhenReady(playWhenReady);
|
|
for (Listener 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) {
|
|
internalPlayer.seekTo(positionMs);
|
|
}
|
|
|
|
@Override
|
|
public void stop() {
|
|
internalPlayer.stop();
|
|
}
|
|
|
|
@Override
|
|
public void release() {
|
|
internalPlayer.release();
|
|
eventHandler.removeCallbacksAndMessages(null);
|
|
}
|
|
|
|
@Override
|
|
public void sendMessage(ExoPlayerComponent target, int messageType, Object message) {
|
|
internalPlayer.sendMessage(target, messageType, message);
|
|
}
|
|
|
|
@Override
|
|
public void blockingSendMessage(ExoPlayerComponent target, int messageType, Object message) {
|
|
internalPlayer.blockingSendMessage(target, messageType, message);
|
|
}
|
|
|
|
@Override
|
|
public long getDuration() {
|
|
return internalPlayer.getDuration();
|
|
}
|
|
|
|
@Override
|
|
public long getCurrentPosition() {
|
|
return internalPlayer.getCurrentPosition();
|
|
}
|
|
|
|
@Override
|
|
public long getBufferedPosition() {
|
|
return internalPlayer.getBufferedPosition();
|
|
}
|
|
|
|
@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_PREPARED: {
|
|
boolean[] rendererHasMediaFlags = (boolean[]) msg.obj;
|
|
System.arraycopy(rendererHasMediaFlags, 0, this.rendererHasMediaFlags, 0,
|
|
rendererHasMediaFlags.length);
|
|
playbackState = msg.arg1;
|
|
for (Listener listener : listeners) {
|
|
listener.onPlayerStateChanged(playWhenReady, playbackState);
|
|
}
|
|
break;
|
|
}
|
|
case ExoPlayerImplInternal.MSG_STATE_CHANGED: {
|
|
playbackState = msg.arg1;
|
|
for (Listener listener : listeners) {
|
|
listener.onPlayerStateChanged(playWhenReady, playbackState);
|
|
}
|
|
break;
|
|
}
|
|
case ExoPlayerImplInternal.MSG_SET_PLAY_WHEN_READY_ACK: {
|
|
pendingPlayWhenReadyAcks--;
|
|
if (pendingPlayWhenReadyAcks == 0) {
|
|
for (Listener listener : listeners) {
|
|
listener.onPlayWhenReadyCommitted();
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case ExoPlayerImplInternal.MSG_ERROR: {
|
|
ExoPlaybackException exception = (ExoPlaybackException) msg.obj;
|
|
for (Listener listener : listeners) {
|
|
listener.onPlayerError(exception);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|