Home | History | Annotate | Download | only in playback
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License
     15  */
     16 
     17 package com.android.tv.dvr.ui.playback;
     18 
     19 import android.media.PlaybackParams;
     20 import android.media.session.PlaybackState;
     21 import android.media.tv.TvContentRating;
     22 import android.media.tv.TvInputManager;
     23 import android.media.tv.TvTrackInfo;
     24 import android.media.tv.TvView;
     25 import android.text.TextUtils;
     26 import android.util.Log;
     27 import com.android.tv.dvr.data.RecordedProgram;
     28 import java.util.ArrayList;
     29 import java.util.List;
     30 import java.util.concurrent.TimeUnit;
     31 
     32 class DvrPlayer {
     33     private static final String TAG = "DvrPlayer";
     34     private static final boolean DEBUG = false;
     35 
     36     /** The max rewinding speed supported by DVR player. */
     37     public static final int MAX_REWIND_SPEED = 256;
     38     /** The max fast-forwarding speed supported by DVR player. */
     39     public static final int MAX_FAST_FORWARD_SPEED = 256;
     40 
     41     private static final long SEEK_POSITION_MARGIN_MS = TimeUnit.SECONDS.toMillis(2);
     42     private static final long REWIND_POSITION_MARGIN_MS = 32; // Workaround value. b/29994826
     43 
     44     private RecordedProgram mProgram;
     45     private long mInitialSeekPositionMs;
     46     private final TvView mTvView;
     47     private DvrPlayerCallback mCallback;
     48     private OnAspectRatioChangedListener mOnAspectRatioChangedListener;
     49     private OnContentBlockedListener mOnContentBlockedListener;
     50     private OnTracksAvailabilityChangedListener mOnTracksAvailabilityChangedListener;
     51     private OnTrackSelectedListener mOnAudioTrackSelectedListener;
     52     private OnTrackSelectedListener mOnSubtitleTrackSelectedListener;
     53     private String mSelectedAudioTrackId;
     54     private String mSelectedSubtitleTrackId;
     55     private float mAspectRatio = Float.NaN;
     56     private int mPlaybackState = PlaybackState.STATE_NONE;
     57     private long mTimeShiftCurrentPositionMs;
     58     private boolean mPauseOnPrepared;
     59     private boolean mHasClosedCaption;
     60     private boolean mHasMultiAudio;
     61     private final PlaybackParams mPlaybackParams = new PlaybackParams();
     62     private final DvrPlayerCallback mEmptyCallback = new DvrPlayerCallback();
     63     private long mStartPositionMs = TvInputManager.TIME_SHIFT_INVALID_TIME;
     64     private boolean mTimeShiftPlayAvailable;
     65 
     66     public static class DvrPlayerCallback {
     67         /**
     68          * Called when the playback position is changed. The normal updating frequency is around 1
     69          * sec., which is restricted to the implementation of {@link
     70          * android.media.tv.TvInputService}.
     71          */
     72         public void onPlaybackPositionChanged(long positionMs) {}
     73         /** Called when the playback state or the playback speed is changed. */
     74         public void onPlaybackStateChanged(int playbackState, int playbackSpeed) {}
     75         /** Called when the playback toward the end. */
     76         public void onPlaybackEnded() {}
     77     }
     78 
     79     public interface OnAspectRatioChangedListener {
     80         /**
     81          * Called when the Video's aspect ratio is changed.
     82          *
     83          * @param videoAspectRatio The aspect ratio of video. 0 stands for unknown ratios. Listeners
     84          *     should handle it carefully.
     85          */
     86         void onAspectRatioChanged(float videoAspectRatio);
     87     }
     88 
     89     public interface OnContentBlockedListener {
     90         /** Called when the Video's aspect ratio is changed. */
     91         void onContentBlocked(TvContentRating rating);
     92     }
     93 
     94     public interface OnTracksAvailabilityChangedListener {
     95         /** Called when the Video's subtitle or audio tracks are changed. */
     96         void onTracksAvailabilityChanged(boolean hasClosedCaption, boolean hasMultiAudio);
     97     }
     98 
     99     public interface OnTrackSelectedListener {
    100         /** Called when certain subtitle or audio track is selected. */
    101         void onTrackSelected(String selectedTrackId);
    102     }
    103 
    104     public DvrPlayer(TvView tvView) {
    105         mTvView = tvView;
    106         mTvView.setCaptionEnabled(true);
    107         mPlaybackParams.setSpeed(1.0f);
    108         setTvViewCallbacks();
    109         setCallback(null);
    110     }
    111 
    112     /**
    113      * Prepares playback.
    114      *
    115      * @param doPlay indicates DVR player do or do not start playback after media is prepared.
    116      */
    117     public void prepare(boolean doPlay) throws IllegalStateException {
    118         if (DEBUG) Log.d(TAG, "prepare()");
    119         if (mProgram == null) {
    120             throw new IllegalStateException("Recorded program not set");
    121         } else if (mPlaybackState != PlaybackState.STATE_NONE) {
    122             throw new IllegalStateException("Playback is already prepared");
    123         }
    124         mTvView.timeShiftPlay(mProgram.getInputId(), mProgram.getUri());
    125         mPlaybackState = PlaybackState.STATE_CONNECTING;
    126         mPauseOnPrepared = !doPlay;
    127         mCallback.onPlaybackStateChanged(mPlaybackState, 1);
    128     }
    129 
    130     /** Resumes playback. */
    131     public void play() throws IllegalStateException {
    132         if (DEBUG) Log.d(TAG, "play()");
    133         if (!isPlaybackPrepared()) {
    134             throw new IllegalStateException("Recorded program not set or video not ready yet");
    135         }
    136         switch (mPlaybackState) {
    137             case PlaybackState.STATE_FAST_FORWARDING:
    138             case PlaybackState.STATE_REWINDING:
    139                 setPlaybackSpeed(1);
    140                 break;
    141             default:
    142                 mTvView.timeShiftResume();
    143         }
    144         mPlaybackState = PlaybackState.STATE_PLAYING;
    145         mCallback.onPlaybackStateChanged(mPlaybackState, 1);
    146     }
    147 
    148     /** Pauses playback. */
    149     public void pause() throws IllegalStateException {
    150         if (DEBUG) Log.d(TAG, "pause()");
    151         if (!isPlaybackPrepared()) {
    152             throw new IllegalStateException("Recorded program not set or playback not started yet");
    153         }
    154         switch (mPlaybackState) {
    155             case PlaybackState.STATE_FAST_FORWARDING:
    156             case PlaybackState.STATE_REWINDING:
    157                 setPlaybackSpeed(1);
    158                 // falls through
    159             case PlaybackState.STATE_PLAYING:
    160                 mTvView.timeShiftPause();
    161                 mPlaybackState = PlaybackState.STATE_PAUSED;
    162                 break;
    163             default:
    164                 break;
    165         }
    166         mCallback.onPlaybackStateChanged(mPlaybackState, 1);
    167     }
    168 
    169     /**
    170      * Fast-forwards playback with the given speed. If the given speed is larger than {@value
    171      * #MAX_FAST_FORWARD_SPEED}, uses {@value #MAX_FAST_FORWARD_SPEED}.
    172      */
    173     public void fastForward(int speed) throws IllegalStateException {
    174         if (DEBUG) Log.d(TAG, "fastForward()");
    175         if (!isPlaybackPrepared()) {
    176             throw new IllegalStateException("Recorded program not set or playback not started yet");
    177         }
    178         if (speed <= 0) {
    179             throw new IllegalArgumentException("Speed cannot be negative or 0");
    180         }
    181         if (mTimeShiftCurrentPositionMs >= mProgram.getDurationMillis() - SEEK_POSITION_MARGIN_MS) {
    182             return;
    183         }
    184         speed = Math.min(speed, MAX_FAST_FORWARD_SPEED);
    185         if (DEBUG) Log.d(TAG, "Let's play with speed: " + speed);
    186         setPlaybackSpeed(speed);
    187         mPlaybackState = PlaybackState.STATE_FAST_FORWARDING;
    188         mCallback.onPlaybackStateChanged(mPlaybackState, speed);
    189     }
    190 
    191     /**
    192      * Rewinds playback with the given speed. If the given speed is larger than {@value
    193      * #MAX_REWIND_SPEED}, uses {@value #MAX_REWIND_SPEED}.
    194      */
    195     public void rewind(int speed) throws IllegalStateException {
    196         if (DEBUG) Log.d(TAG, "rewind()");
    197         if (!isPlaybackPrepared()) {
    198             throw new IllegalStateException("Recorded program not set or playback not started yet");
    199         }
    200         if (speed <= 0) {
    201             throw new IllegalArgumentException("Speed cannot be negative or 0");
    202         }
    203         if (mTimeShiftCurrentPositionMs <= REWIND_POSITION_MARGIN_MS) {
    204             return;
    205         }
    206         speed = Math.min(speed, MAX_REWIND_SPEED);
    207         if (DEBUG) Log.d(TAG, "Let's play with speed: " + speed);
    208         setPlaybackSpeed(-speed);
    209         mPlaybackState = PlaybackState.STATE_REWINDING;
    210         mCallback.onPlaybackStateChanged(mPlaybackState, speed);
    211     }
    212 
    213     /** Seeks playback to the specified position. */
    214     public void seekTo(long positionMs) throws IllegalStateException {
    215         if (DEBUG) Log.d(TAG, "seekTo()");
    216         if (!isPlaybackPrepared()) {
    217             throw new IllegalStateException("Recorded program not set or playback not started yet");
    218         }
    219         if (mProgram == null || mPlaybackState == PlaybackState.STATE_NONE) {
    220             return;
    221         }
    222         positionMs = getRealSeekPosition(positionMs, SEEK_POSITION_MARGIN_MS);
    223         if (DEBUG) Log.d(TAG, "Now: " + getPlaybackPosition() + ", shift to: " + positionMs);
    224         mTvView.timeShiftSeekTo(positionMs + mStartPositionMs);
    225         if (mPlaybackState == PlaybackState.STATE_FAST_FORWARDING
    226                 || mPlaybackState == PlaybackState.STATE_REWINDING) {
    227             mPlaybackState = PlaybackState.STATE_PLAYING;
    228             mTvView.timeShiftResume();
    229             mCallback.onPlaybackStateChanged(mPlaybackState, 1);
    230         }
    231     }
    232 
    233     /** Resets playback. */
    234     public void reset() {
    235         if (DEBUG) Log.d(TAG, "reset()");
    236         mCallback.onPlaybackStateChanged(PlaybackState.STATE_NONE, 1);
    237         mPlaybackState = PlaybackState.STATE_NONE;
    238         mTvView.reset();
    239         mTimeShiftPlayAvailable = false;
    240         mStartPositionMs = TvInputManager.TIME_SHIFT_INVALID_TIME;
    241         mTimeShiftCurrentPositionMs = 0;
    242         mPlaybackParams.setSpeed(1.0f);
    243         mProgram = null;
    244         mSelectedAudioTrackId = null;
    245         mSelectedSubtitleTrackId = null;
    246     }
    247 
    248     /** Sets callbacks for playback. */
    249     public void setCallback(DvrPlayerCallback callback) {
    250         if (callback != null) {
    251             mCallback = callback;
    252         } else {
    253             mCallback = mEmptyCallback;
    254         }
    255     }
    256 
    257     /** Sets the listener to aspect ratio changing. */
    258     public void setOnAspectRatioChangedListener(OnAspectRatioChangedListener listener) {
    259         mOnAspectRatioChangedListener = listener;
    260     }
    261 
    262     /** Sets the listener to content blocking. */
    263     public void setOnContentBlockedListener(OnContentBlockedListener listener) {
    264         mOnContentBlockedListener = listener;
    265     }
    266 
    267     /** Sets the listener to tracks changing. */
    268     public void setOnTracksAvailabilityChangedListener(
    269             OnTracksAvailabilityChangedListener listener) {
    270         mOnTracksAvailabilityChangedListener = listener;
    271     }
    272 
    273     /**
    274      * Sets the listener to tracks of the given type being selected.
    275      *
    276      * @param trackType should be either {@link TvTrackInfo#TYPE_AUDIO} or {@link
    277      *     TvTrackInfo#TYPE_SUBTITLE}.
    278      */
    279     public void setOnTrackSelectedListener(int trackType, OnTrackSelectedListener listener) {
    280         if (trackType == TvTrackInfo.TYPE_AUDIO) {
    281             mOnAudioTrackSelectedListener = listener;
    282         } else if (trackType == TvTrackInfo.TYPE_SUBTITLE) {
    283             mOnSubtitleTrackSelectedListener = listener;
    284         }
    285     }
    286 
    287     /** Gets the listener to tracks of the given type being selected. */
    288     public OnTrackSelectedListener getOnTrackSelectedListener(int trackType) {
    289         if (trackType == TvTrackInfo.TYPE_AUDIO) {
    290             return mOnAudioTrackSelectedListener;
    291         } else if (trackType == TvTrackInfo.TYPE_SUBTITLE) {
    292             return mOnSubtitleTrackSelectedListener;
    293         }
    294         return null;
    295     }
    296 
    297     /** Sets recorded programs for playback. If the player is playing another program, stops it. */
    298     public void setProgram(RecordedProgram program, long initialSeekPositionMs) {
    299         if (mProgram != null && mProgram.equals(program)) {
    300             return;
    301         }
    302         if (mPlaybackState != PlaybackState.STATE_NONE) {
    303             reset();
    304         }
    305         mInitialSeekPositionMs = initialSeekPositionMs;
    306         mProgram = program;
    307     }
    308 
    309     /** Returns the recorded program now playing. */
    310     public RecordedProgram getProgram() {
    311         return mProgram;
    312     }
    313 
    314     /** Returns the currrent playback posistion in msecs. */
    315     public long getPlaybackPosition() {
    316         return mTimeShiftCurrentPositionMs;
    317     }
    318 
    319     /** Returns the playback speed currently used. */
    320     public int getPlaybackSpeed() {
    321         return (int) mPlaybackParams.getSpeed();
    322     }
    323 
    324     /** Returns the playback state defined in {@link android.media.session.PlaybackState}. */
    325     public int getPlaybackState() {
    326         return mPlaybackState;
    327     }
    328 
    329     /** Returns the subtitle tracks of the current playback. */
    330     public ArrayList<TvTrackInfo> getSubtitleTracks() {
    331         return new ArrayList<>(mTvView.getTracks(TvTrackInfo.TYPE_SUBTITLE));
    332     }
    333 
    334     /** Returns the audio tracks of the current playback. */
    335     public ArrayList<TvTrackInfo> getAudioTracks() {
    336         return new ArrayList<>(mTvView.getTracks(TvTrackInfo.TYPE_AUDIO));
    337     }
    338 
    339     /** Returns the ID of the selected track of the given type. */
    340     public String getSelectedTrackId(int trackType) {
    341         if (trackType == TvTrackInfo.TYPE_AUDIO) {
    342             return mSelectedAudioTrackId;
    343         } else if (trackType == TvTrackInfo.TYPE_SUBTITLE) {
    344             return mSelectedSubtitleTrackId;
    345         }
    346         return null;
    347     }
    348 
    349     /** Returns if playback of the recorded program is started. */
    350     public boolean isPlaybackPrepared() {
    351         return mPlaybackState != PlaybackState.STATE_NONE
    352                 && mPlaybackState != PlaybackState.STATE_CONNECTING;
    353     }
    354 
    355     /**
    356      * Selects the given track.
    357      *
    358      * @return ID of the selected track.
    359      */
    360     String selectTrack(int trackType, TvTrackInfo selectedTrack) {
    361         String oldSelectedTrackId = getSelectedTrackId(trackType);
    362         String newSelectedTrackId = selectedTrack == null ? null : selectedTrack.getId();
    363         if (!TextUtils.equals(oldSelectedTrackId, newSelectedTrackId)) {
    364             if (selectedTrack == null) {
    365                 mTvView.selectTrack(trackType, null);
    366                 return null;
    367             } else {
    368                 List<TvTrackInfo> tracks = mTvView.getTracks(trackType);
    369                 if (tracks != null && tracks.contains(selectedTrack)) {
    370                     mTvView.selectTrack(trackType, newSelectedTrackId);
    371                     return newSelectedTrackId;
    372                 } else if (trackType == TvTrackInfo.TYPE_SUBTITLE && oldSelectedTrackId != null) {
    373                     // Track not found, disabled closed caption.
    374                     mTvView.selectTrack(trackType, null);
    375                     return null;
    376                 }
    377             }
    378         }
    379         return oldSelectedTrackId;
    380     }
    381 
    382     private void setSelectedTrackId(int trackType, String trackId) {
    383         if (trackType == TvTrackInfo.TYPE_AUDIO) {
    384             mSelectedAudioTrackId = trackId;
    385         } else if (trackType == TvTrackInfo.TYPE_SUBTITLE) {
    386             mSelectedSubtitleTrackId = trackId;
    387         }
    388     }
    389 
    390     private void setPlaybackSpeed(int speed) {
    391         mPlaybackParams.setSpeed(speed);
    392         mTvView.timeShiftSetPlaybackParams(mPlaybackParams);
    393     }
    394 
    395     private long getRealSeekPosition(long seekPositionMs, long endMarginMs) {
    396         return Math.max(0, Math.min(seekPositionMs, mProgram.getDurationMillis() - endMarginMs));
    397     }
    398 
    399     private void setTvViewCallbacks() {
    400         mTvView.setTimeShiftPositionCallback(
    401                 new TvView.TimeShiftPositionCallback() {
    402                     @Override
    403                     public void onTimeShiftStartPositionChanged(String inputId, long timeMs) {
    404                         if (DEBUG) Log.d(TAG, "onTimeShiftStartPositionChanged:" + timeMs);
    405                         mStartPositionMs = timeMs;
    406                         if (mTimeShiftPlayAvailable) {
    407                             resumeToWatchedPositionIfNeeded();
    408                         }
    409                     }
    410 
    411                     @Override
    412                     public void onTimeShiftCurrentPositionChanged(String inputId, long timeMs) {
    413                         if (DEBUG) Log.d(TAG, "onTimeShiftCurrentPositionChanged: " + timeMs);
    414                         if (!mTimeShiftPlayAvailable) {
    415                             // Workaround of b/31436263
    416                             return;
    417                         }
    418                         // Workaround of b/32211561, TIF won't report start position when TIS report
    419                         // its start position as 0. In that case, we have to do the prework of
    420                         // playback
    421                         // on the first time we get current position, and the start position should
    422                         // be 0
    423                         // at that time.
    424                         if (mStartPositionMs == TvInputManager.TIME_SHIFT_INVALID_TIME) {
    425                             mStartPositionMs = 0;
    426                             resumeToWatchedPositionIfNeeded();
    427                         }
    428                         timeMs -= mStartPositionMs;
    429                         if (mPlaybackState == PlaybackState.STATE_REWINDING
    430                                 && timeMs <= REWIND_POSITION_MARGIN_MS) {
    431                             play();
    432                         } else {
    433                             mTimeShiftCurrentPositionMs = getRealSeekPosition(timeMs, 0);
    434                             mCallback.onPlaybackPositionChanged(mTimeShiftCurrentPositionMs);
    435                             if (timeMs >= mProgram.getDurationMillis()) {
    436                                 pause();
    437                                 mCallback.onPlaybackEnded();
    438                             }
    439                         }
    440                     }
    441                 });
    442         mTvView.setCallback(
    443                 new TvView.TvInputCallback() {
    444                     @Override
    445                     public void onTimeShiftStatusChanged(String inputId, int status) {
    446                         if (DEBUG) Log.d(TAG, "onTimeShiftStatusChanged:" + status);
    447                         if (status == TvInputManager.TIME_SHIFT_STATUS_AVAILABLE
    448                                 && mPlaybackState == PlaybackState.STATE_CONNECTING) {
    449                             mTimeShiftPlayAvailable = true;
    450                             if (mStartPositionMs != TvInputManager.TIME_SHIFT_INVALID_TIME) {
    451                                 // onTimeShiftStatusChanged is sometimes called after
    452                                 // onTimeShiftStartPositionChanged is called. In this case,
    453                                 // resumeToWatchedPositionIfNeeded needs to be called here.
    454                                 resumeToWatchedPositionIfNeeded();
    455                             }
    456                         }
    457                     }
    458 
    459                     @Override
    460                     public void onTracksChanged(String inputId, List<TvTrackInfo> tracks) {
    461                         boolean hasClosedCaption =
    462                                 !mTvView.getTracks(TvTrackInfo.TYPE_SUBTITLE).isEmpty();
    463                         boolean hasMultiAudio =
    464                                 mTvView.getTracks(TvTrackInfo.TYPE_AUDIO).size() > 1;
    465                         if ((hasClosedCaption != mHasClosedCaption
    466                                         || hasMultiAudio != mHasMultiAudio)
    467                                 && mOnTracksAvailabilityChangedListener != null) {
    468                             mOnTracksAvailabilityChangedListener.onTracksAvailabilityChanged(
    469                                     hasClosedCaption, hasMultiAudio);
    470                         }
    471                         mHasClosedCaption = hasClosedCaption;
    472                         mHasMultiAudio = hasMultiAudio;
    473                     }
    474 
    475                     @Override
    476                     public void onTrackSelected(String inputId, int type, String trackId) {
    477                         if (type == TvTrackInfo.TYPE_AUDIO || type == TvTrackInfo.TYPE_SUBTITLE) {
    478                             setSelectedTrackId(type, trackId);
    479                             OnTrackSelectedListener listener = getOnTrackSelectedListener(type);
    480                             if (listener != null) {
    481                                 listener.onTrackSelected(trackId);
    482                             }
    483                         } else if (type == TvTrackInfo.TYPE_VIDEO
    484                                 && trackId != null
    485                                 && mOnAspectRatioChangedListener != null) {
    486                             List<TvTrackInfo> trackInfos =
    487                                     mTvView.getTracks(TvTrackInfo.TYPE_VIDEO);
    488                             if (trackInfos != null) {
    489                                 for (TvTrackInfo trackInfo : trackInfos) {
    490                                     if (trackInfo.getId().equals(trackId)) {
    491                                         float videoAspectRatio;
    492                                         int videoWidth = trackInfo.getVideoWidth();
    493                                         int videoHeight = trackInfo.getVideoHeight();
    494                                         if (videoWidth > 0 && videoHeight > 0) {
    495                                             videoAspectRatio =
    496                                                     trackInfo.getVideoPixelAspectRatio()
    497                                                             * trackInfo.getVideoWidth()
    498                                                             / trackInfo.getVideoHeight();
    499                                         } else {
    500                                             // Aspect ratio is unknown. Pass the message to
    501                                             // listeners.
    502                                             videoAspectRatio = 0;
    503                                         }
    504                                         if (DEBUG) Log.d(TAG, "Aspect Ratio: " + videoAspectRatio);
    505                                         if (mAspectRatio != videoAspectRatio
    506                                                 || videoAspectRatio == 0) {
    507                                             mOnAspectRatioChangedListener.onAspectRatioChanged(
    508                                                     videoAspectRatio);
    509                                             mAspectRatio = videoAspectRatio;
    510                                             return;
    511                                         }
    512                                     }
    513                                 }
    514                             }
    515                         }
    516                     }
    517 
    518                     @Override
    519                     public void onContentBlocked(String inputId, TvContentRating rating) {
    520                         if (mOnContentBlockedListener != null) {
    521                             mOnContentBlockedListener.onContentBlocked(rating);
    522                         }
    523                     }
    524                 });
    525     }
    526 
    527     private void resumeToWatchedPositionIfNeeded() {
    528         if (mInitialSeekPositionMs != TvInputManager.TIME_SHIFT_INVALID_TIME) {
    529             mTvView.timeShiftSeekTo(
    530                     getRealSeekPosition(mInitialSeekPositionMs, SEEK_POSITION_MARGIN_MS)
    531                             + mStartPositionMs);
    532             mInitialSeekPositionMs = TvInputManager.TIME_SHIFT_INVALID_TIME;
    533         }
    534         if (mPauseOnPrepared) {
    535             mTvView.timeShiftPause();
    536             mPlaybackState = PlaybackState.STATE_PAUSED;
    537             mPauseOnPrepared = false;
    538         } else {
    539             mTvView.timeShiftResume();
    540             mPlaybackState = PlaybackState.STATE_PLAYING;
    541         }
    542         mCallback.onPlaybackStateChanged(mPlaybackState, 1);
    543     }
    544 }
    545