org.opensilk.video.playback.PlaybackService.java Source code

Java tutorial

Introduction

Here is the source code for org.opensilk.video.playback.PlaybackService.java

Source

/*
 * Copyright (c) 2016 OpenSilk Productions LLC.
 *
 * This program 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.
 *
 * This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.opensilk.video.playback;

import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.media.MediaDescription;
import android.media.MediaMetadata;
import android.media.Rating;
import android.media.browse.MediaBrowser;
import android.media.session.MediaSession;
import android.media.session.PlaybackState;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Process;
import android.os.ResultReceiver;
import android.view.KeyEvent;

import com.bumptech.glide.Glide;
import com.bumptech.glide.request.FutureTarget;
import com.bumptech.glide.request.RequestOptions;

import org.apache.commons.lang3.StringUtils;
import org.opensilk.common.core.dagger2.ActivityScope;
import org.opensilk.common.core.dagger2.ForApplication;
import org.opensilk.common.core.util.BundleHelper;
import org.opensilk.video.VideoAppPreferences;
import org.opensilk.video.data.DataService;
import org.opensilk.video.data.MediaDescriptionUtil;
import org.opensilk.video.data.MediaMetaExtras;
import org.opensilk.video.data.VideosProviderClient;
import org.opensilk.video.tv.ui.details.DetailsActivity;
import org.opensilk.video.tv.ui.playback.PlaybackActivity;
import org.opensilk.video.util.PlaybackStateHelper;
import org.videolan.libvlc.IVLCVout;
import org.videolan.libvlc.Media;
import org.videolan.libvlc.MediaPlayer;

import java.lang.reflect.Field;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import javax.inject.Inject;

import timber.log.Timber;

import static android.media.session.PlaybackState.ACTION_PAUSE;
import static android.media.session.PlaybackState.ACTION_PLAY;
import static android.media.session.PlaybackState.ACTION_PLAY_FROM_URI;
import static android.media.session.PlaybackState.ACTION_STOP;
import static android.media.session.PlaybackState.STATE_BUFFERING;
import static android.media.session.PlaybackState.STATE_ERROR;
import static android.media.session.PlaybackState.STATE_FAST_FORWARDING;
import static android.media.session.PlaybackState.STATE_NONE;
import static android.media.session.PlaybackState.STATE_PAUSED;
import static android.media.session.PlaybackState.STATE_PLAYING;
import static android.media.session.PlaybackState.STATE_REWINDING;
import static android.media.session.PlaybackState.STATE_SKIPPING_TO_NEXT;
import static android.media.session.PlaybackState.STATE_SKIPPING_TO_PREVIOUS;
import static android.media.session.PlaybackState.STATE_SKIPPING_TO_QUEUE_ITEM;
import static android.media.session.PlaybackState.STATE_STOPPED;

/**
 * Created by drew on 3/21/16.
 */
@ActivityScope
public class PlaybackService {

    public interface ACTION {
        String SEEK_DELTA = "seek_delta";
        String SET_SPU_TRACK = "set_spu_track";
    }

    public interface CMD {
        String GET_SPU_TRACKS = "get_spu_tracks";
    }

    private final Context mContext;
    private final VideoAppPreferences mSettings;
    private final VideosProviderClient mDbClient;
    private final VLCInstance mVLCInstance;
    private final PlaybackQueue mQueue;
    private final DataService mDataService;

    private final IVLCVout.Callback mVLCVOutCallback = new VLCVoutCallback();
    private final MediaPlayerEventListener mMediaPlayerEventListener = new MediaPlayerEventListener();
    private final MediaSessionCallback mMediaSessionCallback = new MediaSessionCallback();

    private MediaSession mMediaSession;
    private MediaPlayer mMediaPlayer;
    private HandlerThread mPlaybackThread;
    private Handler mPlaybackHandler;
    private Handler mMainHandler;

    private boolean mCreated;

    /* for getTime and seek */
    private long mForcedTime = -1;
    private long mLastTime = -1;

    private int mCurrentState = STATE_NONE;
    private float mPlaybackSpeed = 1.0f;
    private boolean mPlayOnMedia;
    private long mSeekOnMedia = -1;
    private boolean mSeekable;
    private boolean mLoadingNext;

    @Inject
    public PlaybackService(@ForApplication Context mContext, VideoAppPreferences mSettings,
            VideosProviderClient mDbClient, VLCInstance vlcInstance, PlaybackQueue queue,
            DataService mDataService) {
        this.mContext = mContext;
        this.mSettings = mSettings;
        this.mDbClient = mDbClient;
        this.mVLCInstance = vlcInstance;
        this.mQueue = queue;
        this.mDataService = mDataService;
    }

    public MediaSession getMediaSession() {
        return mMediaSession;
    }

    private MediaPlayer newMediaPlayer() {
        final MediaPlayer mp = new MediaPlayer(mVLCInstance.get());
        mp.getVLCVout().addCallback(mVLCVOutCallback);
        return mp;
    }

    private MediaSession newMediaSession() {
        final MediaSession mediaSession = new MediaSession(mContext, "SilkVideoPlayer");
        mediaSession
                .setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
        final ComponentName mediaButtonComponent = new ComponentName(mContext, MediaButtonReceiver.class);
        final PendingIntent mediaButtonIntent = PendingIntent.getBroadcast(mContext, 1,
                new Intent().setComponent(mediaButtonComponent), PendingIntent.FLAG_UPDATE_CURRENT);
        mediaSession.setMediaButtonReceiver(mediaButtonIntent);
        mediaSession.setSessionActivity(makeActivityIntent(null));
        return mediaSession;
    }

    public void onCreate() {
        mPlaybackThread = new HandlerThread("SilkPlayback", Process.THREAD_PRIORITY_MORE_FAVORABLE);
        mPlaybackThread.start();
        mPlaybackHandler = new Handler(mPlaybackThread.getLooper());
        mMainHandler = new Handler(Looper.getMainLooper());

        mMediaSession = newMediaSession();
        mMediaSession.setCallback(mMediaSessionCallback, mPlaybackHandler);

        mMediaPlayer = newMediaPlayer();
        mMediaPlayer.setEventListener(mMediaPlayerEventListener);
        mMediaPlayer.setEqualizer(VLCOptions.getEqualizer(mContext));

        mCreated = true;

        updateState(STATE_NONE);

    }

    public void onDestroy() {
        mCreated = false;

        mMediaSession.setActive(false);
        mMediaSession.release();
        mMediaSession = null;

        mMediaPlayer.setEventListener(null);
        mMediaPlayer.stop();
        mMediaPlayer.release();
        mMediaPlayer = null;

        mPlaybackHandler.removeCallbacksAndMessages(null);
        mPlaybackThread.quitSafely();

        mMainHandler.removeCallbacksAndMessages(null);

    }

    public void handleMediaKey(KeyEvent keyEvent) {
        mMediaSession.getController().dispatchMediaButtonEvent(keyEvent);
    }

    PendingIntent makeActivityIntent(MediaBrowser.MediaItem mediaItem) {
        final ComponentName activityComponent = new ComponentName(mContext, PlaybackActivity.class);
        final Intent intent = new Intent().setComponent(activityComponent)
                .setAction(PlaybackActivity.ACTION_RESUME);
        if (mediaItem != null) {
            intent.putExtra(DetailsActivity.MEDIA_ITEM, mediaItem);
        }
        return PendingIntent.getActivity(mContext, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT);
    }

    void updateState(int state) {
        updateState(state, null);
    }

    void updateState(int state, String error) {
        long actions = ACTION_PLAY_FROM_URI;
        switch (state) {
        case STATE_PLAYING:
        case STATE_BUFFERING:
        case STATE_SKIPPING_TO_NEXT:
        case STATE_SKIPPING_TO_PREVIOUS:
        case STATE_SKIPPING_TO_QUEUE_ITEM:
        case STATE_FAST_FORWARDING:
        case STATE_REWINDING:
            actions |= ACTION_PAUSE | ACTION_STOP;
            break;
        case STATE_ERROR:
            break;
        case STATE_STOPPED:
        case STATE_PAUSED:
            actions |= ACTION_PLAY;
            break;
        case STATE_NONE:
            break;
        }
        PlaybackState.Builder builder = new PlaybackState.Builder().setActions(actions).setState(state, getTime(),
                mPlaybackSpeed);
        MediaSession.QueueItem currentItem = mQueue.getCurrent();
        if (currentItem != null) {
            builder.setActiveQueueItemId(currentItem.getQueueId());
        }
        if (state == STATE_ERROR) {
            if (error != null) {
                builder.setErrorMessage(error);
            } else {
                builder.setErrorMessage("Unknown error");
            }
        }
        mMediaSession.setPlaybackState(builder.build());
        mCurrentState = state;
        Timber.d("updateState %s pos=%d", PlaybackStateHelper.stringifyState(state), getTime());
    }

    void updateMetadata() {
        assertCreated();
        final Media media = mMediaPlayer.getMedia();
        final MediaBrowser.MediaItem mediaItem = mDbClient.getMedia(media.getUri());

        final MediaMetadata.Builder b = new MediaMetadata.Builder();
        CharSequence title;
        Uri artworkUri = null;
        long duration;
        if (mediaItem != null) {
            MediaDescription description = mediaItem.getDescription();
            title = description.getTitle();
            b.putText(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, title);
            b.putText(MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE, description.getSubtitle());
            if (description.getIconUri() != null) {
                b.putText(MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI, description.getIconUri().toString());
                artworkUri = description.getIconUri();
            }
            MediaMetaExtras metaExtras = MediaMetaExtras.from(description);
            b.putText(MediaMetadata.METADATA_KEY_TITLE, metaExtras.getMediaTitle());
            duration = metaExtras.getDuration();
        } else {
            title = media.getMeta(Media.Meta.Title);
            b.putText(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, title);
            String artworkUrl = media.getMeta(Media.Meta.ArtworkURL);
            if (!StringUtils.isEmpty(artworkUrl)) {
                b.putText(MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI, artworkUrl);
                artworkUri = Uri.parse(artworkUrl);
            }
            duration = mMediaPlayer.getLength();
        }
        b.putLong(MediaMetadata.METADATA_KEY_DURATION, duration);
        if (artworkUri != null) {
            RequestOptions options = new RequestOptions().fitCenter(mContext);
            FutureTarget<Bitmap> futureTarget = Glide.with(mContext).asBitmap().apply(options).load(artworkUri)
                    .submit();
            try {
                Bitmap bitmap = futureTarget.get(5000, TimeUnit.MILLISECONDS);
                b.putBitmap(MediaMetadata.METADATA_KEY_DISPLAY_ICON, bitmap);
            } catch (InterruptedException | ExecutionException | TimeoutException e) {
                //pass
            }
        }
        mMediaSession.setMetadata(b.build());
        mMediaSession.setSessionActivity(makeActivityIntent(mediaItem));
    }

    private void assertCreated() {
        if (!mCreated) {
            throw new IllegalStateException("No mediaplayer! Must call onCreate()");
        }
    }

    public IVLCVout getVLCVout() {
        assertCreated();
        return mMediaPlayer.getVLCVout();
    }

    private long getTime() {
        assertCreated();
        if (true) {
            return mMediaPlayer.getTime();
        }
        long time = mMediaPlayer.getTime();
        if (mForcedTime != -1 && mLastTime != -1) {
            /* XXX: After a seek, mService.getTime can return the position before or after
             * the seek position. Therefore we return mForcedTime in order to avoid the seekBar
             * to move between seek position and the actual position.
             * We have to wait for a valid position (that is after the seek position).
             * to re-init mLastTime and mForcedTime to -1 and return the actual position.
             */
            if (mLastTime > mForcedTime) {
                if (time <= mLastTime && time > mForcedTime || time > mLastTime)
                    mLastTime = mForcedTime = -1;
            } else {
                if (time > mForcedTime)
                    mLastTime = mForcedTime = -1;
            }
        }
        return mForcedTime == -1 ? time : mForcedTime;
    }

    private void seek(long position) {
        assertCreated();
        seek(position, mMediaPlayer.getLength());
    }

    private void seek(long position, float length) {
        assertCreated();
        mForcedTime = position;
        mLastTime = mMediaPlayer.getTime();
        if (length == 0f) {
            mMediaPlayer.setTime(position);
        } else {
            mMediaPlayer.setPosition(position / length);
        }
    }

    private void seekDelta(int delta) {
        assertCreated();
        if (mMediaPlayer.getLength() <= 0 || !mMediaPlayer.isSeekable()) {
            return; // unseekable stream
        }
        long position = getTime() + delta;
        if (position < 0) {
            position = 0;
        }
        seek(position);
    }

    private static float getSpeedMultiplier(float current) {
        if (current < 2.0f) {
            return 2.0f;
        } else if (current < 3.0f) {
            return 3.0f;
        } else if (current < 4.0f) {
            return 4.0f;
        } else if (current < 5.0f) {
            return 5.0f;
        } else if (current < 6.0f) {
            return 6.0f;
        } else {
            return 2.0f; //rollback round
        }
    }

    private void loadQueueItem(MediaSession.QueueItem queueItem) {
        Uri mediaUri = MediaDescriptionUtil.getMediaUri(queueItem.getDescription());
        final Media media = new Media(mVLCInstance.get(), mediaUri);
        int flags = 0;
        VLCOptions.setMediaOptions(media, mContext, flags);
        mMediaPlayer.setMedia(media);
        mMediaSession.setQueue(mQueue.getQueue());
    }

    private void updateCurrentItemLastPosition(long pos) {
        MediaSession.QueueItem queueItem = mQueue.getCurrent();
        if (queueItem == null) {
            return;
        }
        Uri mediaUri = MediaDescriptionUtil.getMediaUri(queueItem.getDescription());
        Timber.d("Update last_position=%d for %s", pos, mediaUri);
        mDbClient.updateMediaLastPosition(mediaUri, pos);
        mDataService.notifyChange(mediaUri);
    }

    private void updateCurrentItemDuration(long dur) {
        if (dur <= 0) {
            Timber.w("Invalid duration %d", dur);
            return;
        }
        MediaSession.QueueItem queueItem = mQueue.getCurrent();
        if (queueItem == null) {
            return;
        }
        Uri mediaUri = MediaDescriptionUtil.getMediaUri(queueItem.getDescription());
        Timber.d("Update duration=%d for %s", dur, mediaUri);
        mDbClient.updateMediaDuration(mediaUri, dur);
        mDataService.notifyChange(mediaUri);
    }

    class MediaSessionCallback extends MediaSession.Callback {
        public MediaSessionCallback() {
            super();
        }

        @Override
        public void onCommand(String command, Bundle args, ResultReceiver cb) {
            Timber.d("onCommand(%s)", command);
            switch (command) {
            case CMD.GET_SPU_TRACKS: {
                MediaPlayer.TrackDescription[] tracks = mMediaPlayer.getSpuTracks();
                if (tracks != null && tracks.length > 0) {
                    for (MediaPlayer.TrackDescription t : tracks) {
                        cb.send(1, BundleHelper.b().tag("spu_track").putInt(t.id).putString(t.name).get());
                    }
                } else {
                    cb.send(0, null);
                }
                break;
            }
            }
        }

        @Override
        public boolean onMediaButtonEvent(Intent mediaButtonIntent) {
            Timber.d("onMediaButtonEvent()");
            return super.onMediaButtonEvent(mediaButtonIntent);
        }

        @Override
        public void onPlay() {
            Timber.d("onPlay()");
            mMediaSession.setActive(true);
            if (mMediaPlayer.isPlaying()) {
                mMediaPlayer.setRate(1.0f);
                mPlaybackSpeed = 1.0f;
                updateState(STATE_PLAYING);
            } else {
                mMediaPlayer.play();
            }
            //            updateState(STATE_PLAYING);
        }

        @Override
        public void onPlayFromMediaId(String mediaId, Bundle extras) {
            Timber.d("onPlayFromMediaId(%s)", mediaId);
        }

        @Override
        public void onPlayFromSearch(String query, Bundle extras) {
            Timber.d("onPlayFromSearch(%s)", query);
        }

        @Override
        public void onPlayFromUri(Uri uri, Bundle extras) {
            Timber.d("onPlayFromUri(%s)", uri);
            onPause();
            mQueue.loadFromUri(uri);
            MediaSession.QueueItem queueItem = mQueue.getCurrent();
            if (queueItem == null) {
                updateState(STATE_ERROR, "Failed to load queue");
                return;
            }
            mMediaSession.setQueueTitle(mQueue.getTitle());

            mPlayOnMedia = true;
            mSeekOnMedia = -1;
            boolean resume = extras.getBoolean("resume");
            if (resume) {
                MediaMetaExtras metaExtras = MediaMetaExtras.from(queueItem.getDescription());
                if (metaExtras.getLastPosition() > 0) {
                    mSeekOnMedia = metaExtras.getLastPosition();
                }
            }
            loadQueueItem(queueItem);
            onPlay();
        }

        @Override
        public void onSkipToQueueItem(long id) {
            Timber.d("onSkipToQueueItem(%d)", id);
            onPause();
            mQueue.moveToItem(id);
            MediaSession.QueueItem queueItem = mQueue.getCurrent();
            if (queueItem == null) {
                updateState(STATE_ERROR, "Failed to load queue");
                return;
            }
            loadQueueItem(queueItem);
            onPlay();
        }

        @Override
        public void onPause() {
            Timber.d("onPause()");
            updateCurrentItemLastPosition(getTime());
            mMediaPlayer.pause();
            updateState(STATE_PAUSED);
        }

        @Override
        public void onSkipToNext() {
            Timber.d("onSkipToNext()");
            onPlay();
            MediaSession.QueueItem queueItem = mQueue.getNext();
            if (queueItem == null) {
                updateState(STATE_ERROR, "Unable to get next queue item");
                return;
            }
            updateState(STATE_SKIPPING_TO_NEXT);
            loadQueueItem(queueItem);
            onPlay();
        }

        @Override
        public void onSkipToPrevious() {
            Timber.d("onSkipToPrevious()");
            onPause();
            MediaSession.QueueItem queueItem = mQueue.getPrevious();
            if (queueItem == null) {
                updateState(STATE_ERROR, "Unable to get previous queue item");
                return;
            }
            updateState(STATE_SKIPPING_TO_NEXT);
            loadQueueItem(queueItem);
            onPlay();
        }

        @Override
        public void onFastForward() {
            Timber.d("onFastForward()");
            if (!mMediaPlayer.isPlaying()) {
                mMediaPlayer.play();
            }
            if (mPlaybackSpeed < 0.0f) {
                onPlay();
                return;
            }
            mPlaybackSpeed = getSpeedMultiplier(Math.abs(mPlaybackSpeed));
            Timber.d("onFastForward(%.02f)", mPlaybackSpeed);
            mMediaPlayer.setRate(mPlaybackSpeed);
            updateState(STATE_FAST_FORWARDING);
        }

        @Override
        public void onRewind() {
            Timber.d("onRewind()");
            if (!mMediaPlayer.isPlaying()) {
                mMediaPlayer.play();
            }
            if (mPlaybackSpeed > 0.0f) {
                onPlay();
                return;
            }
            mPlaybackSpeed = -getSpeedMultiplier(Math.abs(mPlaybackSpeed));
            Timber.d("onRewind(%.02f)", mPlaybackSpeed);
            mMediaPlayer.setRate(mPlaybackSpeed);
            updateState(STATE_REWINDING);
        }

        @Override
        public void onStop() {
            Timber.d("onStop()");
            updateCurrentItemLastPosition(getTime());
            mMediaPlayer.stop();
            updateState(STATE_STOPPED);
        }

        @Override
        public void onSeekTo(long pos) {
            Timber.d("onSeekTo(%d)", pos);
            seek(pos);
        }

        @Override
        public void onSetRating(Rating rating) {
            Timber.d("onSetRating(%s)", rating);
        }

        @Override
        public void onCustomAction(String action, Bundle extras) {
            Timber.d("onCustomAction(%s)", action);
            switch (action) {
            case ACTION.SEEK_DELTA: {
                int delta = BundleHelper.getInt(extras);
                seekDelta(delta);
                mPlaybackHandler.post(() -> {
                    updateState(mCurrentState);
                });
                break;
            }
            case ACTION.SET_SPU_TRACK: {
                int track = BundleHelper.getInt(extras);
                if (mMediaPlayer.getSpuTrack() != track) {
                    mMediaPlayer.setSpuTrack(track);
                }
                break;
            }
            }
        }
    }

    class MediaPlayerEventListener implements MediaPlayer.EventListener {
        @Override
        public void onEvent(MediaPlayer.Event event) {
            mPlaybackHandler.post(() -> {
                switch (event.type) {
                case MediaPlayer.Event.Opening:
                    Timber.i("MediaPlayer.Event.Opening");
                    updateState(STATE_BUFFERING);
                    break;
                case MediaPlayer.Event.MediaChanged:
                    Timber.i("MediaPlayer.Event.MediaChanged");
                    mLoadingNext = false;
                    //                        updateMetadata();
                    break;
                case MediaPlayer.Event.Playing:
                    Timber.i("MediaPlayer.Event.Playing");
                    if (mCurrentState != STATE_PLAYING) {
                        updateState(STATE_PLAYING);
                    }
                    //                        updateCurrentItemDuration(mMediaPlayer.getLength());
                    //                        updateMetadata();//to get duration
                    break;
                case MediaPlayer.Event.Paused:
                    Timber.i("MediaPlayer.Event.Paused");
                    if (mCurrentState != STATE_PAUSED) {
                        updateState(STATE_PAUSED);
                    }
                    break;
                case MediaPlayer.Event.Stopped:
                    Timber.i("MediaPlayer.Event.Stopped");
                    if (!mLoadingNext && mCurrentState != STATE_STOPPED) {
                        updateState(STATE_STOPPED);
                    }
                    break;
                case MediaPlayer.Event.EndReached:
                    Timber.i("MediaPlayer.Event.EndReached");
                    updateCurrentItemLastPosition(mMediaPlayer.getLength());
                    MediaSession.QueueItem queueItem = mQueue.getNext();
                    if (queueItem != null) {
                        mLoadingNext = true;
                        loadQueueItem(queueItem);
                        mMediaSessionCallback.onPlay();
                    }
                    break;
                case MediaPlayer.Event.EncounteredError:
                    Timber.i("MediaPlayer.Event.EncounteredError");
                    break;
                case MediaPlayer.Event.TimeChanged:
                    //                    Timber.i("MediaPlayer.Event.TimeChanged");
                    break;
                case MediaPlayer.Event.PositionChanged:
                    //                    Timber.i("MediaPlayer.Event.PositionChanged");
                    break;
                case MediaPlayer.Event.Vout:
                    Timber.i("MediaPlayer.Event.Vout count=%d", event.getVoutCount());
                    break;
                case MediaPlayer.Event.ESAdded:
                    Timber.i("MediaPlayer.Event.ESAdded");
                    break;
                case MediaPlayer.Event.ESDeleted:
                    Timber.i("MediaPlayer.Event.ESDeleted");
                    break;
                case MediaPlayer.Event.PausableChanged:
                    Timber.i("MediaPlayer.Event.PausableChanged pausable=%s", event.getPausable());
                    updateCurrentItemDuration(mMediaPlayer.getLength());
                    updateMetadata();//to get duration
                    break;
                case MediaPlayer.Event.SeekableChanged:
                    Timber.i("MediaPlayer.Event.SeekableChanged seekable=%s", event.getSeekable());
                    if (event.getSeekable() && mSeekOnMedia > 0) {
                        mMediaSessionCallback.onSeekTo(mSeekOnMedia);
                    }
                    mSeekOnMedia = -1;
                    updateCurrentItemDuration(mMediaPlayer.getLength());
                    updateMetadata();//to get duration
                    break;
                default:
                    try {
                        Field[] eventFields = MediaPlayer.Event.class.getDeclaredFields();
                        for (Field f : eventFields) {
                            int val = f.getInt(null);
                            if (val == event.type) {
                                Timber.w("onEvent(%s)[Unhandled]", f.getName());
                                return;
                            }
                        }
                        Timber.e("onEvent(%d)[Unknown]", event.type);
                    } catch (Exception e) {
                        Timber.w(e, "onEvent");
                    }
                }
            });
        }
    }

    class VLCVoutCallback implements IVLCVout.Callback {
        @Override
        public void onNewLayout(IVLCVout ivlcVout, int width, int height, int visibleWidth, int visibleHeight,
                int sarNum, int sarDen) {

        }

        @Override
        public void onSurfacesCreated(IVLCVout ivlcVout) {

        }

        @Override
        public void onSurfacesDestroyed(IVLCVout ivlcVout) {

        }

        @Override
        public void onHardwareAccelerationError(IVLCVout ivlcVout) {
            Timber.e("onHardwareAccelerationError()");
        }
    }
}