butter.droid.base.fragments.BaseVideoPlayerFragment.java Source code

Java tutorial

Introduction

Here is the source code for butter.droid.base.fragments.BaseVideoPlayerFragment.java

Source

/*
 * This file is part of Butter.
 *
 * Butter is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Butter is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Butter. If not, see <http://www.gnu.org/licenses/>.
 */

package butter.droid.base.fragments;

import android.annotation.TargetApi;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Canvas;
import android.graphics.PorterDuff;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.design.widget.Snackbar;
import android.support.v4.app.Fragment;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;

import com.connectsdk.device.ConnectableDevice;
import com.github.sv244.torrentstream.StreamStatus;
import com.github.sv244.torrentstream.Torrent;
import com.github.sv244.torrentstream.listeners.TorrentListener;

import org.videolan.libvlc.IVLCVout;
import org.videolan.libvlc.LibVLC;
import org.videolan.libvlc.MediaPlayer;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Locale;

import butter.droid.base.ButterApplication;
import butter.droid.base.R;
import butter.droid.base.beaming.BeamDeviceListener;
import butter.droid.base.beaming.BeamManager;
import butter.droid.base.content.preferences.Prefs;
import butter.droid.base.fragments.dialog.FileSelectorDialogFragment;
import butter.droid.base.fragments.dialog.NumberPickerDialogFragment;
import butter.droid.base.fragments.dialog.StringArraySelectorDialogFragment;
import butter.droid.base.providers.media.models.Media;
import butter.droid.base.providers.subs.SubsProvider;
import butter.droid.base.subs.Caption;
import butter.droid.base.subs.SubtitleDownloader;
import butter.droid.base.subs.TimedTextObject;
import butter.droid.base.torrent.StreamInfo;
import butter.droid.base.torrent.TorrentService;
import butter.droid.base.utils.FragmentUtil;
import butter.droid.base.utils.LocaleUtils;
import butter.droid.base.utils.PrefUtils;
import butter.droid.base.vlc.VLCInstance;
import butter.droid.base.vlc.VLCOptions;
import timber.log.Timber;

public abstract class BaseVideoPlayerFragment extends Fragment
        implements IVLCVout.Callback, TorrentListener, MediaPlayer.EventListener, LibVLC.HardwareAccelerationError,
        SubtitleDownloader.ISubtitleDownloaderListener {

    public static final String RESUME_POSITION = "resume_position";
    public static final int SUBTITLE_MINIMUM_SIZE = 10;

    private LibVLC mLibVLC;
    private MediaPlayer mMediaPlayer;
    private Long mResumePosition = 0l;
    private Long mDuration = 0l;

    private static final int SURFACE_BEST_FIT = 0;
    private static final int SURFACE_FIT_HORIZONTAL = 1;
    private static final int SURFACE_FIT_VERTICAL = 2;
    private static final int SURFACE_FILL = 3;
    private static final int SURFACE_16_9 = 4;
    private static final int SURFACE_4_3 = 5;
    private static final int SURFACE_ORIGINAL = 6;

    protected Media mMedia;
    protected boolean mShowReload = false;

    private int mCurrentSize = SURFACE_BEST_FIT;
    private int mStreamerProgress = 0;

    private String mCurrentSubsLang = SubsProvider.SUBTITLE_LANGUAGE_NONE;
    private TimedTextObject mSubs;
    private Caption mLastSub = null;
    private File mSubsFile = null;

    private boolean mEnded = false;
    private boolean mSeeking = false;
    private boolean mMediaReady = false;

    private int mVideoHeight;
    private int mVideoWidth;
    private int mVideoVisibleHeight;
    private int mVideoVisibleWidth;
    private int mSarNum;
    private int mSarDen;
    private int mSubtitleOffset = 0;

    // probably required when hardware acceleration selection during playback is implemented
    @SuppressWarnings("FieldCanBeLocal")
    private boolean mDisabledHardwareAcceleration = false;

    protected Callback mCallback;

    private View mRootView;

    private static LibVLC LibVLC() {
        return VLCInstance.get(ButterApplication.getAppContext());
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        if (context instanceof Callback)
            mCallback = (Callback) context;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRetainInstance(true);
        setHasOptionsMenu(true);
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
            @Nullable Bundle savedInstanceState) {
        return super.onCreateView(inflater, container, savedInstanceState);
    }

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        mRootView = view;
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        if (getActivity() instanceof Callback && mCallback == null) {
            mCallback = (Callback) getActivity();
        }

        mResumePosition = mCallback.getResumePosition();
        PrefUtils.save(getActivity(), RESUME_POSITION, mResumePosition);

        if (!VLCInstance.hasCompatibleCPU(getContext())) {
            getActivity().finish();
            return;
        }

        StreamInfo streamInfo = mCallback.getInfo();

        if (streamInfo == null) {
            getActivity().finish();
            return;
        }

        mMedia = streamInfo.getMedia();

        mLibVLC = LibVLC();
        mLibVLC.setOnHardwareAccelerationError(this);

        mMediaPlayer = new MediaPlayer(mLibVLC);
        mMediaPlayer.setEventListener(this);

        if (mCallback.getService() != null) {
            mCallback.getService().addListener(this);
        }

        setProgressVisible(true);

        if (null == streamInfo.getSubtitleLanguage()) {
            // Get selected default subtitle
            String defaultSubtitle = PrefUtils.get(getActivity(), Prefs.SUBTITLE_DEFAULT,
                    SubsProvider.SUBTITLE_LANGUAGE_NONE);
            streamInfo.setSubtitleLanguage(defaultSubtitle);
            mCurrentSubsLang = defaultSubtitle;
            Timber.d("Using default subtitle: " + mCurrentSubsLang);
        }

        if (!streamInfo.getSubtitleLanguage().equals(SubsProvider.SUBTITLE_LANGUAGE_NONE)) {
            Timber.d("Download default subtitle");
            mCurrentSubsLang = streamInfo.getSubtitleLanguage();
            loadOrDownloadSubtitle();
        }

        updateSubtitleSize(PrefUtils.get(getActivity(), Prefs.SUBTITLE_SIZE,
                getResources().getInteger(R.integer.player_subtitles_default_text_size)));

        prepareVlcVout();
        if (mMediaReady)
            loadMedia();
    }

    @Override
    public void onResume() {
        super.onResume();

        prepareVlcVout();
        BeamManager.getInstance(getActivity()).addDeviceListener(mDeviceListener);
        onProgressChanged(PrefUtils.get(getActivity(), RESUME_POSITION, mResumePosition), mDuration);
    }

    @Override
    public void onPause() {
        super.onPause();

        saveVideoCurrentTime();
        mMediaPlayer.stop();

        final IVLCVout vlcout = mMediaPlayer.getVLCVout();
        vlcout.removeCallback(this);
        vlcout.detachViews();

        BeamManager.getInstance(getActivity()).removeDeviceListener(mDeviceListener);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();

        PrefUtils.save(getActivity(), RESUME_POSITION, 0);

        mMediaPlayer.release();
        mLibVLC.release();

        if (mCallback.getService() != null) {
            mCallback.getService().removeListener(this);
        }
    }

    public void onMediaReady() {
        if (!mMediaReady) {
            mMediaReady = true;
        }
        if (mLibVLC != null) {
            loadMedia();
        }
    }

    protected void disableHardwareAcceleration() {
        mDisabledHardwareAcceleration = true;
        saveVideoCurrentTime();
    }

    /**
     * External extras: - position (long) - position of the video to start with (in ms)
     */
    @SuppressWarnings({ "unchecked" })
    protected void loadMedia() {
        StreamInfo streamInfo = mCallback.getInfo();

        String videoLocation;
        if (TextUtils.isEmpty(streamInfo.getVideoLocation())) {
            Toast.makeText(getActivity(), "Error loading media", Toast.LENGTH_LONG).show();
            getActivity().finish();
            return;
        } else {
            videoLocation = streamInfo.getVideoLocation();
            if (!videoLocation.startsWith("file://") && !videoLocation.startsWith("http://")
                    && !videoLocation.startsWith("https://")) {
                videoLocation = "file://" + videoLocation;
            }
        }

        int flags = mDisabledHardwareAcceleration ? VLCOptions.MEDIA_NO_HWACCEL : 0;
        flags = flags | VLCOptions.MEDIA_VIDEO;

        org.videolan.libvlc.Media media = new org.videolan.libvlc.Media(mLibVLC, Uri.parse(videoLocation));
        VLCOptions.setMediaOptions(media, getActivity(), flags);

        mMediaPlayer.setMedia(media);

        long resumeFrom = PrefUtils.get(getActivity(), RESUME_POSITION, mResumePosition);
        if (resumeFrom > 0) {
            mMediaPlayer.setTime(resumeFrom);
        }

        mDuration = mMediaPlayer.getLength();
        mMediaPlayer.play();
        mEnded = false;
    }

    protected abstract void setProgressVisible(boolean visible);

    protected abstract void showOverlay();

    protected abstract void showPlayerInfo(String info);

    protected abstract void onProgressChanged(long currentTime, long duration);

    protected abstract void updatePlayPauseState();

    protected abstract void onErrorEncountered();

    protected abstract void onHardwareAccelerationError();

    protected abstract void showTimedCaptionText(Caption text);

    protected abstract SurfaceView getVideoSurface();

    protected void setSeeking(boolean seeking) {
        mSeeking = seeking;
    }

    protected boolean isSeeking() {
        return mSeeking;
    }

    private void prepareVlcVout() {
        final IVLCVout vlcVout = mMediaPlayer.getVLCVout();

        if (!vlcVout.areViewsAttached()) {
            vlcVout.setVideoView(getVideoSurface());
            vlcVout.addCallback(this);
            vlcVout.attachViews();
        }
    }

    private void saveVideoCurrentTime() {
        long currentTime = mMediaPlayer.getTime();
        PrefUtils.save(getActivity(), RESUME_POSITION, currentTime);
    }

    private void resumeVideo() {
        if (getActivity() != null) {
            long resumePosition = PrefUtils.get(getActivity(), RESUME_POSITION, 0);
            mDuration = mMediaPlayer.getLength();
            if (mDuration > resumePosition && resumePosition > 0) {
                setCurrentTime(resumePosition);
                PrefUtils.save(getActivity(), RESUME_POSITION, 0);
            }
        }
    }

    public void play() {
        mMediaPlayer.play();
    }

    public void pause() {
        mMediaPlayer.pause();
    }

    public void togglePlayPause() {
        if (mEnded) {
            loadMedia();
        }

        if (mMediaPlayer.isPlaying()) {
            pause();
        } else {
            play();
        }
    }

    public void seekForwardClick() {
        seek(10000);
    }

    public void seekBackwardClick() {
        seek(-10000);
    }

    public void scaleClick() {
        if (mCurrentSize < SURFACE_ORIGINAL) {
            mCurrentSize++;
        } else {
            mCurrentSize = 0;
        }
        changeSurfaceSize(true);
        showOverlay();
    }

    protected void setCurrentTime(long time) {
        if (time / getDuration() * 100 <= getStreamerProgress()) {
            mMediaPlayer.setTime(time);
        }
    }

    protected long getCurrentTime() {
        return mMediaPlayer.getTime();
    }

    protected long getDuration() {
        return mDuration;
    }

    public int getStreamerProgress() {
        return mStreamerProgress;
    }

    /**
     * Is a video currently playing with VLC
     *
     * @return true if video is played using VLC
     */
    protected boolean isPlaying() {
        return mMediaPlayer.isPlaying();
    }

    private void endReached() {
        mEnded = true;
        onPlaybackEndReached();
    }

    public abstract void onPlaybackEndReached();

    private void handleHardwareAccelerationError() {
        saveVideoCurrentTime();
        mMediaPlayer.stop();
        onHardwareAccelerationError();
    }

    @SuppressWarnings("SuspiciousNameCombination")
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
    private void changeSurfaceSize(boolean message) {
        int screenWidth = getActivity().getWindow().getDecorView().getWidth();
        int screenHeight = getActivity().getWindow().getDecorView().getHeight();

        if (mMediaPlayer != null) {
            final IVLCVout vlcVout = mMediaPlayer.getVLCVout();
            vlcVout.setWindowSize(screenWidth, screenHeight);
        }

        double displayWidth = screenWidth, displayHeight = screenHeight;

        if (screenWidth < screenHeight) {
            displayWidth = screenHeight;
            displayHeight = screenWidth;
        }

        // sanity check
        if (displayWidth * displayHeight <= 1 || mVideoWidth * mVideoHeight <= 1) {
            Timber.e("Invalid surface size");
            onErrorEncountered();
            return;
        }

        // compute the aspect ratio
        double aspectRatio, visibleWidth;
        if (mSarDen == mSarNum) {
            /* No indication about the density, assuming 1:1 */
            visibleWidth = mVideoVisibleWidth;
            aspectRatio = (double) mVideoVisibleWidth / (double) mVideoVisibleHeight;
        } else {
            /* Use the specified aspect ratio */
            visibleWidth = mVideoVisibleWidth * (double) mSarNum / mSarDen;
            aspectRatio = visibleWidth / mVideoVisibleHeight;
        }

        // compute the display aspect ratio
        double displayAspectRatio = displayWidth / displayHeight;

        switch (mCurrentSize) {
        case SURFACE_BEST_FIT:
            if (message)
                showPlayerInfo(getString(R.string.best_fit));
            if (displayAspectRatio < aspectRatio)
                displayHeight = displayWidth / aspectRatio;
            else
                displayWidth = displayHeight * aspectRatio;
            break;
        case SURFACE_FIT_HORIZONTAL:
            displayHeight = displayWidth / aspectRatio;
            if (message)
                showPlayerInfo(getString(R.string.fit_horizontal));
            break;
        case SURFACE_FIT_VERTICAL:
            displayWidth = displayHeight * aspectRatio;
            if (message)
                showPlayerInfo(getString(R.string.fit_vertical));
            break;
        case SURFACE_FILL:
            if (message)
                showPlayerInfo(getString(R.string.fill));
            break;
        case SURFACE_16_9:
            if (message)
                showPlayerInfo("16:9");
            aspectRatio = 16.0 / 9.0;
            if (displayAspectRatio < aspectRatio)
                displayHeight = displayWidth / aspectRatio;
            else
                displayWidth = displayHeight * aspectRatio;
            break;
        case SURFACE_4_3:
            if (message)
                showPlayerInfo("4:3");
            aspectRatio = 4.0 / 3.0;
            if (displayAspectRatio < aspectRatio)
                displayHeight = displayWidth / aspectRatio;
            else
                displayWidth = displayHeight * aspectRatio;
            break;
        case SURFACE_ORIGINAL:
            if (message)
                showPlayerInfo(getString(R.string.original_size));
            displayHeight = mVideoVisibleHeight;
            displayWidth = visibleWidth;
            break;
        }

        // set display size
        int finalWidth = (int) Math.ceil(displayWidth * mVideoWidth / mVideoVisibleWidth);
        int finalHeight = (int) Math.ceil(displayHeight * mVideoHeight / mVideoVisibleHeight);

        SurfaceHolder holder = getVideoSurface().getHolder();
        holder.setFixedSize(finalWidth, finalHeight);

        ViewGroup.LayoutParams lp = getVideoSurface().getLayoutParams();
        lp.width = finalWidth;
        lp.height = finalHeight;
        getVideoSurface().setLayoutParams(lp);
        getVideoSurface().invalidate();
    }

    protected void seek(int delta) {
        if (mMediaPlayer.getLength() <= 0 && !mSeeking)
            return;

        long position = mMediaPlayer.getTime() + delta;
        if (position < 0)
            position = 0;
        setCurrentTime(position);
        showOverlay();
        onProgressChanged(getCurrentTime(), getDuration());
        mLastSub = null;
    }

    protected void setLastSubtitleCaption(Caption sub) {
        mLastSub = sub;
    }

    protected void progressSubtitleCaption() {
        if (mLibVLC != null && mMediaPlayer != null && mMediaPlayer.isPlaying() && mSubs != null) {
            Collection<Caption> subtitles = mSubs.captions.values();
            double currentTime = getCurrentTime() - mSubtitleOffset;
            if (mLastSub != null && currentTime >= mLastSub.start.getMilliseconds()
                    && currentTime <= mLastSub.end.getMilliseconds()) {
                showTimedCaptionText(mLastSub);
            } else {
                for (Caption caption : subtitles) {
                    if (currentTime >= caption.start.getMilliseconds()
                            && currentTime <= caption.end.getMilliseconds()) {
                        mLastSub = caption;
                        showTimedCaptionText(caption);
                        break;
                    } else if (currentTime > caption.end.getMilliseconds()) {
                        showTimedCaptionText(null);
                    }
                }
            }
        }
    }

    protected void onSubtitleLanguageSelected(String language) {
        if (mCurrentSubsLang != null && (language == null || mCurrentSubsLang.equals(language))) {
            return;
        }

        mCurrentSubsLang = language;
        mCallback.getInfo().setSubtitleLanguage(mCurrentSubsLang);

        if (mCurrentSubsLang.equals(SubsProvider.SUBTITLE_LANGUAGE_NONE)) {
            mSubs = null;
            onSubtitleEnabledStateChanged(false);
            return;
        }

        if (mMedia == null || mMedia.subtitles == null || mMedia.subtitles.size() == 0) {
            mSubs = null;
            onSubtitleEnabledStateChanged(false);
            throw new IllegalArgumentException("Media doesn't have subtitle");
        }

        if (!mMedia.subtitles.containsKey(mCurrentSubsLang)) {
            mSubs = null;
            onSubtitleEnabledStateChanged(false);
            throw new IllegalArgumentException("Media doesn't have subtitle with specified language");
        }

        showTimedCaptionText(null);
        loadOrDownloadSubtitle();
    }

    private void loadOrDownloadSubtitle() {
        if (mMedia == null)
            throw new NullPointerException("Media is not available");
        if (mCurrentSubsLang.equals(SubsProvider.SUBTITLE_LANGUAGE_NONE))
            return;

        SubtitleDownloader subtitleDownloader = new SubtitleDownloader(getActivity(), mCallback.getInfo(),
                mCurrentSubsLang);
        subtitleDownloader.setSubtitleDownloaderListener(this);

        try {
            mSubsFile = SubtitleDownloader.getDownloadedSubtitleFile(getActivity(), mMedia, mCurrentSubsLang);
            if (mSubsFile != null && mSubsFile.exists()) {
                subtitleDownloader.parseSubtitle(mSubsFile);
            }
        } catch (FileNotFoundException e) {
            subtitleDownloader.downloadSubtitle();
        }
    }

    /**
     * This callback is called when the native vout call request a new Layout.
     *
     * @param vlcVout vlcVout
     * @param width Frame width
     * @param height Frame height
     * @param visibleWidth Visible frame width
     * @param visibleHeight Visible frame height
     * @param sarNum Surface aspect ratio numerator
     * @param sarDen Surface aspect ratio denominator
     */
    @Override
    public void onNewLayout(IVLCVout vlcVout, int width, int height, int visibleWidth, int visibleHeight,
            int sarNum, int sarDen) {
        if (width * height <= 0) {
            return;
        }

        // store video size
        mVideoWidth = width;
        mVideoHeight = height;
        mVideoVisibleWidth = visibleWidth;
        mVideoVisibleHeight = visibleHeight;
        mSarNum = sarNum;
        mSarDen = sarDen;

        changeSurfaceLayout();
    }

    /**
     * Called when subtitle for current media successfully loaded or disabled.
     * @param enabled Whether subtitle is loaded or disabled.
     */
    protected void onSubtitleEnabledStateChanged(boolean enabled) {
    }

    @Override
    public void onSurfacesCreated(IVLCVout ivlcVout) {
    }

    @Override
    public void onSurfacesDestroyed(IVLCVout ivlcVout) {
    }

    @Override
    public void onEvent(MediaPlayer.Event event) {
        switch (event.type) {
        case MediaPlayer.Event.Playing:
            getVideoSurface().getHolder().setKeepScreenOn(true);
            mDuration = mMediaPlayer.getLength();
            resumeVideo();
            setProgressVisible(false);
            showOverlay();
            updatePlayPauseState();
            break;
        case MediaPlayer.Event.Paused:
            getVideoSurface().getHolder().setKeepScreenOn(false);
            saveVideoCurrentTime();
            updatePlayPauseState();
            break;
        case MediaPlayer.Event.Stopped:
            getVideoSurface().getHolder().setKeepScreenOn(false);
            updatePlayPauseState();
            break;
        case MediaPlayer.Event.EndReached:
            endReached();
            updatePlayPauseState();
            break;
        case MediaPlayer.Event.EncounteredError:
            onErrorEncountered();
            updatePlayPauseState();
            break;
        case MediaPlayer.Event.Opening:
            setProgressVisible(true);
            mDuration = mMediaPlayer.getLength();
            mMediaPlayer.play();
            break;
        case MediaPlayer.Event.TimeChanged:
        case MediaPlayer.Event.PositionChanged:
            onProgressChanged(getCurrentTime(), getDuration());
            progressSubtitleCaption();
            break;
        }
    }

    @Override
    public void eventHardwareAccelerationError() {
        handleHardwareAccelerationError();
    }

    private void changeSurfaceLayout() {
        changeSurfaceSize(false);
    }

    @Override
    public void onStreamPrepared(Torrent torrent) {
    }

    @Override
    public void onStreamStarted(Torrent torrent) {
    }

    @Override
    public void onStreamStopped() {
    }

    @Override
    public void onStreamError(Torrent torrent, Exception e) {
    }

    @Override
    public void onStreamReady(Torrent torrent) {
    }

    @Override
    public void onStreamProgress(Torrent torrent, StreamStatus streamStatus) {
        int newProgress = (int) ((getDuration() / 100) * streamStatus.progress);
        if (mStreamerProgress < newProgress) {
            mStreamerProgress = newProgress;
        }
    }

    public interface Callback {
        Long getResumePosition();

        StreamInfo getInfo();

        TorrentService getService();
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        super.onCreateOptionsMenu(menu, inflater);
        inflater.inflate(R.menu.activity_player, menu);
        menu.findItem(R.id.action_reload).setVisible(mShowReload);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int i = item.getItemId();
        if (i == R.id.action_reload) {
            mMediaPlayer.stop();
            Canvas canvas = new Canvas();
            canvas.drawColor(0, PorterDuff.Mode.CLEAR);
            getVideoSurface().draw(canvas);
            loadMedia();
            return true;
        }
        return super.onOptionsItemSelected(item);
    }

    protected void subsClick() {
        if (mMedia != null && mMedia.subtitles != null) {
            if (getChildFragmentManager().findFragmentByTag("overlay_fragment") != null)
                return;

            String[] subsOptions = { getString(R.string.subtitle_language), getString(R.string.subtitle_size),
                    getString(R.string.subtitle_timing) };

            StringArraySelectorDialogFragment.show(getChildFragmentManager(), R.string.subtitle_settings,
                    subsOptions, -1, new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int position) {
                            switch (position) {
                            case 0:
                                subsLanguageSettings();
                                break;
                            case 1:
                                subsSizeSettings();
                                break;
                            case 2:
                                subsTimingSettings();
                                break;
                            }
                        }
                    });
        }
    }

    private void subsLanguageSettings() {
        String[] subtitles = mMedia.subtitles.keySet().toArray(new String[mMedia.subtitles.size()]);
        Arrays.sort(subtitles);
        final String[] adapterSubtitles = new String[subtitles.length + 2];
        System.arraycopy(subtitles, 0, adapterSubtitles, 1, subtitles.length);

        adapterSubtitles[0] = SubsProvider.SUBTITLE_LANGUAGE_NONE;
        adapterSubtitles[adapterSubtitles.length - 1] = "custom";
        String[] readableNames = new String[adapterSubtitles.length];

        for (int i = 0; i < readableNames.length - 1; i++) {
            String language = adapterSubtitles[i];
            if (language.equals(SubsProvider.SUBTITLE_LANGUAGE_NONE)) {
                readableNames[i] = getString(R.string.no_subs);
            } else {
                Locale locale = LocaleUtils.toLocale(language);
                readableNames[i] = locale.getDisplayName(locale);
            }
        }

        readableNames[readableNames.length - 1] = "Custom..";

        StringArraySelectorDialogFragment.showSingleChoice(getChildFragmentManager(), R.string.subtitles,
                readableNames, Arrays.asList(adapterSubtitles).indexOf(mCurrentSubsLang),
                new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(final DialogInterface dialog, int position) {
                        if (position == adapterSubtitles.length - 1) {
                            FileSelectorDialogFragment.show(getChildFragmentManager(),
                                    new FileSelectorDialogFragment.Listener() {
                                        @Override
                                        public void onFileSelected(File f) {
                                            if (!f.getPath().endsWith(".srt")) {
                                                Snackbar.make(mRootView, R.string.unknown_error,
                                                        Snackbar.LENGTH_SHORT).show();
                                                return;
                                            }
                                            FileSelectorDialogFragment.hide();
                                            mSubsFile = f;
                                            dialog.dismiss();
                                        }
                                    });
                            return;
                        }
                        onSubtitleLanguageSelected(adapterSubtitles[position]);
                        dialog.dismiss();
                    }
                });
    }

    private void subsSizeSettings() {
        Bundle args = new Bundle();
        args.putString(NumberPickerDialogFragment.TITLE, getString(R.string.subtitle_size));
        args.putInt(NumberPickerDialogFragment.MAX_VALUE, 60);
        args.putInt(NumberPickerDialogFragment.MIN_VALUE, SUBTITLE_MINIMUM_SIZE);
        args.putInt(NumberPickerDialogFragment.DEFAULT_VALUE,
                getResources().getInteger(R.integer.player_subtitles_default_text_size));

        NumberPickerDialogFragment dialogFragment = new NumberPickerDialogFragment();
        dialogFragment.setArguments(args);
        dialogFragment.setOnResultListener(new NumberPickerDialogFragment.ResultListener() {
            @Override
            public void onNewValue(int value) {
                updateSubtitleSize(value);
            }
        });
        dialogFragment.show(getChildFragmentManager(), "overlay_fragment");
    }

    private void subsTimingSettings() {
        Bundle args = new Bundle();
        args.putString(NumberPickerDialogFragment.TITLE, getString(R.string.subtitle_timing));
        args.putInt(NumberPickerDialogFragment.MAX_VALUE, 3600);
        args.putInt(NumberPickerDialogFragment.MIN_VALUE, -3600);
        args.putInt(NumberPickerDialogFragment.DEFAULT_VALUE, mSubtitleOffset / 60);
        args.putBoolean(NumberPickerDialogFragment.FOCUSABLE, true);

        NumberPickerDialogFragment dialogFragment = new NumberPickerDialogFragment();
        dialogFragment.setArguments(args);
        dialogFragment.setOnResultListener(new NumberPickerDialogFragment.ResultListener() {
            @Override
            public void onNewValue(int value) {
                mSubtitleOffset = value * 60;
                showTimedCaptionText(null);
            }
        });
        dialogFragment.show(getChildFragmentManager(), "overlay_fragment");
    }

    protected abstract void updateSubtitleSize(int size);

    @Override
    public void onSubtitleDownloadCompleted(boolean isSuccessful, TimedTextObject subtitleFile) {
        onSubtitleEnabledStateChanged(isSuccessful);
        mSubs = subtitleFile;
    }

    BeamDeviceListener mDeviceListener = new BeamDeviceListener() {

        @Override
        public void onDeviceReady(ConnectableDevice device) {
            super.onDeviceReady(device);

            if (!FragmentUtil.isAdded(BaseVideoPlayerFragment.this)) {
                return;
            }

            startBeamPlayerActivity();

            getActivity().finish();
        }
    };

    public abstract void startBeamPlayerActivity();
}