VideoView2ImplApi28WithMp1.java

/*
 * Copyright 2018 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 androidx.media.widget;

import android.content.Context;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnSubtitleDataListener;
import android.media.SubtitleData;
import android.net.Uri;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Pair;
import android.view.View;
import android.view.ViewGroup.LayoutParams;

import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.media.subtitle.ClosedCaptionRenderer;
import androidx.media.subtitle.SubtitleController;
import androidx.media.subtitle.SubtitleTrack;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Map;

/**
 * Base implementation of VideoView2.
 */
@RequiresApi(28)
class VideoView2ImplApi28WithMp1 extends VideoView2ImplBaseWithMp1 {
    private static final String TAG = "VideoView2ImplApi28_1";
    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);

    private static final int INVALID_TRACK_INDEX = -1;

    private ArrayList<Pair<Integer, SubtitleTrack>> mSubtitleTrackIndices;
    private SubtitleController mSubtitleController;

    // selected video/audio/subtitle track index as MediaPlayer returns
    private int mSelectedSubtitleTrackIndex;

    private SubtitleView mSubtitleView;
    private boolean mSubtitleEnabled;

    @Override
    public void initialize(
            VideoView2 instance, Context context,
            @Nullable AttributeSet attrs, int defStyleAttr) {
        super.initialize(instance, context, attrs, defStyleAttr);
        mSelectedSubtitleTrackIndex = INVALID_TRACK_INDEX;

        LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT,
                LayoutParams.MATCH_PARENT);
        mSubtitleView = new SubtitleView(context);
        mSubtitleView.setLayoutParams(params);
        mSubtitleView.setBackgroundColor(0);
        mInstance.addView(mSubtitleView);

        mSubtitleEnabled = (attrs == null) || attrs.getAttributeBooleanValue(
                "http://schemas.android.com/apk/res/android",
                "enableSubtitle", false);
    }

    /**
     * Shows or hides closed caption or subtitles if there is any.
     * The first subtitle track will be chosen if there multiple subtitle tracks exist.
     * Default behavior of VideoView2 is not showing subtitle.
     * @param enable shows closed caption or subtitles if this value is true, or hides.
     */
    @Override
    public void setSubtitleEnabled(boolean enable) {
        if (enable != mSubtitleEnabled) {
            selectOrDeselectSubtitle(enable);
        }
        mSubtitleEnabled = enable;
    }

    /**
     * Returns true if showing subtitle feature is enabled or returns false.
     * Although there is no subtitle track or closed caption, it can return true, if the feature
     * has been enabled by {@link #setSubtitleEnabled}.
     */
    @Override
    public boolean isSubtitleEnabled() {
        return mSubtitleEnabled;
    }

    ///////////////////////////////////////////////////
    // Protected or private methods
    ///////////////////////////////////////////////////

    /**
     * Used in openVideo(). Setup MediaPlayer and related objects before calling prepare.
     */
    @Override
    protected void setupMediaPlayer(Context context, Uri uri, Map<String, String> headers)
            throws IOException {
        super.setupMediaPlayer(context, uri, headers);

        mSubtitleController = new SubtitleController(context);
        mSubtitleController.registerRenderer(new ClosedCaptionRenderer(context));
        mSubtitleController.setAnchor((SubtitleController.Anchor) mSubtitleView);

        mMediaPlayer.setOnSubtitleDataListener(mSubtitleListener);
    }

    private void selectOrDeselectSubtitle(boolean select) {
        if (!isInPlaybackState()) {
            return;
        }
        if (select) {
            if (mSubtitleTrackIndices.size() > 0) {
                mSelectedSubtitleTrackIndex = mSubtitleTrackIndices.get(0).first;
                mSubtitleController.selectTrack(mSubtitleTrackIndices.get(0).second);
                mMediaPlayer.selectTrack(mSelectedSubtitleTrackIndex);
                mSubtitleView.setVisibility(View.VISIBLE);
            }
        } else {
            if (mSelectedSubtitleTrackIndex != INVALID_TRACK_INDEX) {
                mMediaPlayer.deselectTrack(mSelectedSubtitleTrackIndex);
                mSelectedSubtitleTrackIndex = INVALID_TRACK_INDEX;
                mSubtitleView.setVisibility(View.GONE);
            }
        }
    }

    @Override
    protected void extractTracks() {
        MediaPlayer.TrackInfo[] trackInfos = mMediaPlayer.getTrackInfo();
        mVideoTrackIndices = new ArrayList<>();
        mAudioTrackIndices = new ArrayList<>();
        mSubtitleTrackIndices = new ArrayList<>();
        mSubtitleController.reset();
        for (int i = 0; i < trackInfos.length; ++i) {
            int trackType = trackInfos[i].getTrackType();
            if (trackType == MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_VIDEO) {
                mVideoTrackIndices.add(i);
            } else if (trackType == MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_AUDIO) {
                mAudioTrackIndices.add(i);
            } else if (trackType == MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE) {
                SubtitleTrack track = mSubtitleController.addTrack(trackInfos[i].getFormat());
                if (track != null) {
                    mSubtitleTrackIndices.add(new Pair<>(i, track));
                }
            }
        }
        // Select first tracks as default
        if (mVideoTrackIndices.size() > 0) {
            mSelectedVideoTrackIndex = 0;
        }
        if (mAudioTrackIndices.size() > 0) {
            mSelectedAudioTrackIndex = 0;
        }

        Bundle data = new Bundle();
        data.putInt(MediaControlView2.KEY_VIDEO_TRACK_COUNT, mVideoTrackIndices.size());
        data.putInt(MediaControlView2.KEY_AUDIO_TRACK_COUNT, mAudioTrackIndices.size());
        data.putInt(MediaControlView2.KEY_SUBTITLE_TRACK_COUNT, mSubtitleTrackIndices.size());
        if (mSubtitleTrackIndices.size() > 0) {
            selectOrDeselectSubtitle(mSubtitleEnabled);
        }
        mMediaSession.sendSessionEvent(MediaControlView2.EVENT_UPDATE_TRACK_STATUS, data);
    }

    private OnSubtitleDataListener mSubtitleListener =
            new OnSubtitleDataListener() {
                @Override
                public void onSubtitleData(MediaPlayer mp, SubtitleData data) {
                    if (DEBUG) {
                        Log.d(TAG, "onSubtitleData(): getTrackIndex: " + data.getTrackIndex()
                                + ", getCurrentPosition: " + mp.getCurrentPosition()
                                + ", getStartTimeUs(): " + data.getStartTimeUs()
                                + ", diff: "
                                + (data.getStartTimeUs() / 1000 - mp.getCurrentPosition())
                                + "ms, getDurationUs(): " + data.getDurationUs());

                    }
                    final int index = data.getTrackIndex();
                    if (index != mSelectedSubtitleTrackIndex) {
                        Log.d(TAG, "onSubtitleData(): getTrackIndex: " + data.getTrackIndex()
                                + ", selected track index: " + mSelectedSubtitleTrackIndex);
                        return;
                    }
                    for (Pair<Integer, SubtitleTrack> p : mSubtitleTrackIndices) {
                        if (p.first == index) {
                            SubtitleTrack track = p.second;
                            track.onData(data);
                        }
                    }
                }
            };

    @Override
    protected void doShowSubtitleCommand(Bundle args) {
        int subtitleIndex = args.getInt(
                MediaControlView2.KEY_SELECTED_SUBTITLE_INDEX,
                INVALID_TRACK_INDEX);
        if (subtitleIndex != INVALID_TRACK_INDEX) {
            int subtitleTrackIndex = mSubtitleTrackIndices.get(subtitleIndex).first;
            if (subtitleTrackIndex != mSelectedSubtitleTrackIndex) {
                mSelectedSubtitleTrackIndex = subtitleTrackIndex;
                mInstance.setSubtitleEnabled(true);
            }
        }
    }

    @Override
    protected void doHideSubtitleCommand() {
        mInstance.setSubtitleEnabled(false);
    }
}