com.scooter1556.sms.androidtv.fragment.VideoPlayerFragment.java Source code

Java tutorial

Introduction

Here is the source code for com.scooter1556.sms.androidtv.fragment.VideoPlayerFragment.java

Source

/*
 * Author: Scott Ware <scoot.software@gmail.com>
 * Copyright (c) 2015 Scott Ware
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package com.scooter1556.sms.androidtv.fragment;

import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.support.v17.leanback.app.PlaybackOverlayFragment;
import android.support.v17.leanback.widget.AbstractDetailsDescriptionPresenter;
import android.support.v17.leanback.widget.Action;
import android.support.v17.leanback.widget.ArrayObjectAdapter;
import android.support.v17.leanback.widget.ClassPresenterSelector;
import android.support.v17.leanback.widget.ControlButtonPresenterSelector;
import android.support.v17.leanback.widget.ListRow;
import android.support.v17.leanback.widget.ListRowPresenter;
import android.support.v17.leanback.widget.OnActionClickedListener;
import android.support.v17.leanback.widget.PlaybackControlsRow;
import android.support.v17.leanback.widget.PlaybackControlsRow.FastForwardAction;
import android.support.v17.leanback.widget.PlaybackControlsRow.PlayPauseAction;
import android.support.v17.leanback.widget.PlaybackControlsRow.RewindAction;
import android.support.v17.leanback.widget.PlaybackControlsRowPresenter;
import android.view.KeyEvent;
import android.view.SurfaceHolder;
import android.view.View;
import android.view.WindowManager;
import android.widget.Toast;

import com.bumptech.glide.Glide;
import com.bumptech.glide.request.animation.GlideAnimation;
import com.bumptech.glide.request.target.SimpleTarget;
import com.google.android.exoplayer.ExoPlayer;
import com.google.gson.Gson;
import com.loopj.android.http.JsonHttpResponseHandler;
import com.loopj.android.http.TextHttpResponseHandler;
import com.scooter1556.sms.androidtv.R;
import com.scooter1556.sms.androidtv.activity.VideoPlayerActivity;
import com.scooter1556.sms.lib.android.domain.MediaElement;
import com.scooter1556.sms.lib.android.domain.TranscodeProfile;
import com.scooter1556.sms.lib.android.player.SMSVideoPlayer;
import com.scooter1556.sms.lib.android.service.RESTService;

import org.json.JSONObject;

import java.util.Timer;
import java.util.TimerTask;

public class VideoPlayerFragment extends android.support.v17.leanback.app.PlaybackOverlayFragment
        implements SurfaceHolder.Callback, SMSVideoPlayer.Listener {
    static final String SUPPORTED_CODECS = "h264,vp8,aac,mp3,vorbis";
    static final String MCH_CODECS = "ac3";
    static final String FORMAT = "matroska";

    static final int MAX_SAMPLE_RATE = 48000;

    private static final int CARD_SIZE = 240;

    private static final int BACKGROUND_TYPE = PlaybackOverlayFragment.BG_LIGHT;

    private static final int DEFAULT_UPDATE_PERIOD = 1000;

    private static final int DEFAULT_SEEK_DELAY = 1000;
    private static final int DEFAULT_SEEK_SPEED = 10000;
    private static final int MAX_SEEK_SPEED = 300000;

    // REST Client
    RESTService restService = null;

    // Player
    private SMSVideoPlayer player;
    private MediaElement mediaElement;

    // Override automatic controller visibility
    private Boolean initialised = false;
    private Boolean ready = false;
    private Boolean paused = false;

    private ArrayObjectAdapter rowsAdapter;
    private ArrayObjectAdapter primaryActionsAdapter;
    private ArrayObjectAdapter secondaryActionsAdapter;
    private FastForwardAction fastForwardAction;
    private PlayPauseAction playPauseAction;
    private RewindAction rewindAction;
    private PlaybackControlsRow playbackControlsRow;
    private Handler handler;
    private Runnable runnable;

    // Seek
    private Handler seekHandler;
    private Runnable seekRunnable;
    private int seekSpeed = DEFAULT_SEEK_SPEED;
    private boolean seekInProgress = false;

    private SurfaceHolder surfaceHolder;

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

        restService = RESTService.getInstance();

        player = new SMSVideoPlayer();

        surfaceHolder = ((VideoPlayerActivity) getActivity()).getSurfaceHolder();
        surfaceHolder.addCallback(this);

        handler = new Handler();
        seekHandler = new Handler();

        // Retrieve media element to play
        mediaElement = (MediaElement) getActivity().getIntent().getSerializableExtra("MediaElement");

        setBackgroundType(BACKGROUND_TYPE);
        setFadingEnabled(false);
        setupRows();
    }

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

        // If our surface is ready start playing video (surface is not destroyed if the power button is pressed)
        if (ready) {
            preparePlayer();
        }
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        view.setOnKeyListener(new View.OnKeyListener() {
            @Override
            public boolean onKey(View v, int keyCode, KeyEvent event) {
                if (event.getAction() == KeyEvent.ACTION_DOWN) {
                    if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) {
                        if (player != null) {
                            if (player.isPlaying()) {
                                player.pause();
                            } else {
                                player.start();
                            }
                        }
                        return true;
                    }
                }
                return false;
            }
        });
    }

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

        stopProgressAutomation();

        if (player != null) {
            if (player.getTranscodeProfile() != null) {
                restService.endJob(player.getTranscodeProfile().getID());
            }

            releasePlayer();
        }
    }

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

        stopProgressAutomation();
        setFadingEnabled(false);
        paused = true;

        if (player != null) {
            player.setOffset(player.getCurrentPosition());
            player.removeListener(this);
            player.release();
        }
    }

    private void setupRows() {

        ClassPresenterSelector presenter = new ClassPresenterSelector();

        PlaybackControlsRowPresenter playbackControlsRowPresenter;
        playbackControlsRowPresenter = new PlaybackControlsRowPresenter(new DescriptionPresenter());

        playbackControlsRowPresenter.setOnActionClickedListener(new OnActionClickedListener() {
            public void onActionClicked(Action action) {
                if (action.getId() == playPauseAction.getId()) {
                    if (seekInProgress && player != null) {
                        seekTo(playbackControlsRow.getCurrentTime());
                    } else {
                        togglePlayback();
                    }
                } else if (action.getId() == fastForwardAction.getId()) {
                    if (player != null) {
                        if (seekInProgress) {
                            if (seekSpeed > 0) {
                                seekSpeed = seekSpeed * 2;
                                if (seekSpeed > MAX_SEEK_SPEED) {
                                    seekSpeed = MAX_SEEK_SPEED;
                                }
                            } else {
                                seekSpeed = DEFAULT_SEEK_SPEED;
                            }
                        } else {
                            seekSpeed = DEFAULT_SEEK_SPEED;
                            startSeeking();
                        }
                    }
                } else if (action.getId() == rewindAction.getId()) {
                    if (player != null) {
                        if (seekInProgress) {
                            if (seekSpeed < 0) {
                                seekSpeed = seekSpeed * 2;
                                if (seekSpeed < (MAX_SEEK_SPEED * -1)) {
                                    seekSpeed = (MAX_SEEK_SPEED * -1);
                                }
                            } else {
                                seekSpeed = (DEFAULT_SEEK_SPEED * -1);
                            }
                        } else {
                            seekSpeed = (DEFAULT_SEEK_SPEED * -1);
                            startSeeking();
                        }
                    }
                }
            }
        });

        presenter.addClassPresenter(PlaybackControlsRow.class, playbackControlsRowPresenter);
        presenter.addClassPresenter(ListRow.class, new ListRowPresenter());
        rowsAdapter = new ArrayObjectAdapter(presenter);

        addPlaybackControlsRow();

        setAdapter(rowsAdapter);
    }

    private void addPlaybackControlsRow() {
        playbackControlsRow = new PlaybackControlsRow(mediaElement);
        playbackControlsRow.setCurrentTime(0);
        playbackControlsRow.setTotalTime(mediaElement.getDuration() * 1000);

        rowsAdapter.add(playbackControlsRow);

        ControlButtonPresenterSelector presenterSelector = new ControlButtonPresenterSelector();
        primaryActionsAdapter = new ArrayObjectAdapter(presenterSelector);
        playbackControlsRow.setPrimaryActionsAdapter(primaryActionsAdapter);

        playPauseAction = new PlayPauseAction(getActivity());
        fastForwardAction = new FastForwardAction(getActivity());
        rewindAction = new RewindAction(getActivity());

        // Add main controls to primary adapter.
        primaryActionsAdapter.add(rewindAction);
        primaryActionsAdapter.add(playPauseAction);
        primaryActionsAdapter.add(fastForwardAction);

        updateVideoImage();
    }

    private void startProgressAutomation() {
        if (runnable == null) {
            runnable = new Runnable() {
                @Override
                public void run() {
                    int currentTime = (int) player.getCurrentPosition();
                    playbackControlsRow.setCurrentTime(currentTime);

                    handler.postDelayed(this, DEFAULT_UPDATE_PERIOD);
                }
            };

            handler.postDelayed(runnable, DEFAULT_UPDATE_PERIOD);
        }
    }

    private void stopProgressAutomation() {
        if (handler != null && runnable != null) {
            handler.removeCallbacks(runnable);
            runnable = null;
        }
    }

    private void startSeeking() {
        if (!player.isPlaying()) {
            return;
        }

        player.pause();
        seekInProgress = true;
        setFadingEnabled(false);
        stopProgressAutomation();
        playPauseAction.setIndex(PlayPauseAction.PLAY);
        rowsAdapter.notifyArrayItemRangeChanged(rowsAdapter.indexOf(playbackControlsRow), 1);

        if (seekRunnable == null) {
            seekRunnable = new Runnable() {
                @Override
                public void run() {
                    int currentTime = playbackControlsRow.getCurrentTime() + seekSpeed;

                    if (currentTime < 0) {
                        seekTo(0);
                    } else if (currentTime > playbackControlsRow.getTotalTime()) {
                        releasePlayer();
                        getActivity().finish();
                    } else {
                        playbackControlsRow.setCurrentTime(currentTime);
                        seekHandler.postDelayed(this, DEFAULT_SEEK_DELAY);
                    }
                }
            };

            seekHandler.postDelayed(seekRunnable, DEFAULT_SEEK_DELAY);
        }
    }

    private void stopSeeking() {
        if (seekHandler != null && seekRunnable != null) {
            seekHandler.removeCallbacks(seekRunnable);
            seekRunnable = null;
            seekInProgress = false;
        }
    }

    private void updateVideoImage() {
        Glide.with(getActivity()).load(RESTService.getInstance().getConnection().getUrl() + "/image/"
                + mediaElement.getID() + "/cover/" + CARD_SIZE).asBitmap()
                .into(new SimpleTarget<Bitmap>(CARD_SIZE, CARD_SIZE) {
                    @Override
                    public void onResourceReady(Bitmap resource, GlideAnimation<? super Bitmap> glideAnimation) {
                        playbackControlsRow.setImageBitmap(getActivity(), resource);
                        rowsAdapter.notifyArrayItemRangeChanged(0, rowsAdapter.size());
                    }
                });
    }

    private void initialiseVideo() {
        // Get settings
        final SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(getActivity());

        if (mediaElement == null) {
            Toast error = Toast.makeText(getActivity(), getString(R.string.error_loading_media),
                    Toast.LENGTH_SHORT);
            error.show();
        }

        // Initialise Stream
        restService.initialiseStream(mediaElement.getID(), null, SUPPORTED_CODECS, null, FORMAT,
                Integer.parseInt(settings.getString("pref_video_quality", "0")), MAX_SAMPLE_RATE, null, null,
                settings.getBoolean("pref_direct_play", false), new JsonHttpResponseHandler() {

                    @Override
                    public void onSuccess(int statusCode, cz.msebera.android.httpclient.Header[] headers,
                            JSONObject response) {
                        // Parse profile
                        Gson parser = new Gson();
                        TranscodeProfile profile = parser.fromJson(response.toString(), TranscodeProfile.class);

                        // Configure Player
                        player.setTranscodeProfile(profile);
                        player.setID(mediaElement.getID());
                        player.setQuality(settings.getString("pref_video_quality", "0"));
                        player.setMultiChannelEnabled(settings.getBoolean("pref_audio_multichannel", false));

                        if (mediaElement.getDuration() != null) {
                            player.setDuration(mediaElement.getDuration());
                        }

                        preparePlayer();
                        initialised = true;
                    }

                    @Override
                    public void onFailure(int statusCode, cz.msebera.android.httpclient.Header[] headers,
                            String responseString, Throwable throwable) {
                        Toast error = Toast.makeText(getActivity(), getString(R.string.error_loading_media),
                                Toast.LENGTH_SHORT);
                        error.show();
                    }
                });
    }

    private void preparePlayer() {
        String streamUrl = restService.getConnection().getUrl() + "/stream/" + player.getTranscodeProfile().getID()
                + "?offset=" + (int) (player.getOffset() * 0.001);
        Uri contentUri = Uri.parse(streamUrl);

        player.addListener(this);
        player.initialise(getActivity(), contentUri, surfaceHolder.getSurface(), !paused);
    }

    private void releasePlayer() {
        if (player != null) {
            player.release();
            player = null;
        }
    }

    private void togglePlayback() {
        if (player != null) {
            if (player.isPlaying()) {
                player.pause();
                playPauseAction.setIndex(PlayPauseAction.PLAY);
                stopProgressAutomation();
                setFadingEnabled(false);
                getActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
                paused = true;
            } else {
                player.start();
                playPauseAction.setIndex(PlayPauseAction.PAUSE);
                startProgressAutomation();
                setFadingEnabled(true);
                getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
                paused = false;
            }

            rowsAdapter.notifyArrayItemRangeChanged(rowsAdapter.indexOf(playbackControlsRow), 1);
        }
    }

    private void seekTo(int position) {
        if (player != null) {
            // Stop seeking operations
            stopSeeking();

            // Generate new stream uri
            String streamUrl = restService.getConnection().getUrl() + "/stream/"
                    + player.getTranscodeProfile().getID() + "?offset=" + (int) (position * 0.001);
            Uri contentUri = Uri.parse(streamUrl);

            // Initialise player in new position
            player.release();
            player.setOffset(position);
            player.initialise(getActivity(), contentUri, surfaceHolder.getSurface(), !paused);
        }
    }

    @Override
    public void onStateChanged(boolean playWhenReady, int playbackState) {
        switch (playbackState) {
        case ExoPlayer.STATE_BUFFERING:
            break;
        case ExoPlayer.STATE_ENDED:
            releasePlayer();
            getActivity().finish();
            break;
        case ExoPlayer.STATE_IDLE:
            break;
        case ExoPlayer.STATE_PREPARING:
            break;
        case ExoPlayer.STATE_READY:
            // Set timeline
            playbackControlsRow.setCurrentTime((int) player.getCurrentPosition());

            if (player.isPlaying()) {
                playPauseAction.setIndex(PlayPauseAction.PAUSE);
                setFadingEnabled(true);
                getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
                startProgressAutomation();
            } else {
                playPauseAction.setIndex(PlayPauseAction.PLAY);
            }

            rowsAdapter.notifyArrayItemRangeChanged(rowsAdapter.indexOf(playbackControlsRow), 1);

            break;

        default:
            break;
        }
    }

    @Override
    public void onError(Exception e) {
        // Display error message
        Toast warning = Toast.makeText(getActivity(), getString(R.string.error_media_playback), Toast.LENGTH_SHORT);
        warning.show();

        // Release player and finish activity
        releasePlayer();
        getActivity().finish();
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        if (initialised) {
            paused = true;
            preparePlayer();
        } else {
            initialiseVideo();
        }

        // Surface is ready
        ready = true;
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        // Do nothing.
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        if (player != null) {
            ready = false;
            player.release();
        }
    }

    private static final class DescriptionPresenter extends AbstractDetailsDescriptionPresenter {
        @Override
        protected void onBindDescription(ViewHolder viewHolder, Object item) {
            MediaElement element = ((MediaElement) item);

            if (element != null) {
                viewHolder.getTitle().setText(element.getTitle());
            }
        }
    }
}