Contents
Video Controls minimum implementation
Video is streamed in VideoView.
* I referred Google’s latest sample Android TV application. AOSP sample application implementation is defferent.
For Video controls, we have several stuff to explain.
- Action’s UI update part (this chapter)
- Video control part (this chapter)
- MediaSession implementation, Video control via MediaController’s TransportControls (next chapter)
– MediaSession can handle the action when user presses TV remote controller’s video control button.
– It allows other activity to inherite video control. Especially LeanbackLauncher, Home display, can play video in background. - set MediaMetadata to MediaSession (next chapter)
– “Now playing card” will appear at the top of recommendation row.
In this chapter, Video controls implementation is explained. Since Google’s sample application implements all 1~4, source code is bit long and difficult to understand for beginners. I did a minimum implementation for only 1~2. I will just explain these implementation for each part in this chapter, so please download & refer source code on github at first (I did a refactoring for using MovieProvider class to prepare movie contents). The Technic in this chapter is general in Android.
Next chapter I will explain about MediaSession implementation. We can pass VideoView control to LeanbackLauncher by using MediaSession, which results to achieve playing video background in LeanbackLauncher.
VideoView handling
PlaybackOverlayActivity need to have VideoView field variable “mVideoView” to control video.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
public class PlaybackOverlayActivity extends Activity { private static final String TAG = PlaybackOverlayActivity.class.getSimpleName(); private VideoView mVideoView; private LeanbackPlaybackState mPlaybackState = LeanbackPlaybackState.IDLE; private int mPosition = 0; private long mStartTimeMillis; private long mDuration = -1; /* * List of various states that we can be in */ public enum LeanbackPlaybackState { PLAYING, PAUSED, IDLE } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_playback_overlay); loadViews(); } @Override public void onDestroy() { super.onDestroy(); stopPlayback(); mVideoView.suspend(); mVideoView.setVideoURI(null); } private void loadViews() { mVideoView = (VideoView) findViewById(R.id.videoView); mVideoView.setFocusable(false); mVideoView.setFocusableInTouchMode(false); Movie movie = (Movie) getIntent().getSerializableExtra(DetailsActivity.MOVIE); setVideoPath(movie.getVideoUrl()); } public void setVideoPath(String videoUrl) { setPosition(0); mVideoView.setVideoPath(videoUrl); mStartTimeMillis = 0; mDuration = Utils.getDuration(videoUrl); } private void stopPlayback() { if (mVideoView != null) { mVideoView.stopPlayback(); } } |
Implement setOnActionClickedListener & onActionClicked callback
To assign action of each video control button, we use setOnActionClickedListener which is a method of PlaybackControlsRowPresenter.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
private void setUpRows() { ... /* add ListRow to second row of mRowsAdapter */ addOtherRows(); /* onClick */ playbackControlsRowPresenter.setOnActionClickedListener(new OnActionClickedListener() { public void onActionClicked(Action action) { if (action.getId() == mPlayPauseAction.getId()) { /* PlayPause action */ togglePlayback(mPlayPauseAction.getIndex() == PlaybackControlsRow.PlayPauseAction.PLAY); } else if (action.getId() == mSkipNextAction.getId()) { /* SkipNext action */ next(mCurrentPlaybackState == PlaybackState.STATE_PLAYING); } else if (action.getId() == mSkipPreviousAction.getId()) { /* SkipPrevious action */ prev(mCurrentPlaybackState == PlaybackState.STATE_PLAYING); } else if (action.getId() == mFastForwardAction.getId()) { /* FastForward action */ fastForward(); } else if (action.getId() == mRewindAction.getId()) { /* Rewind action */ rewind(); } if (action instanceof PlaybackControlsRow.MultiAction) { /* Following action is subclass of MultiAction * - PlayPauseAction * - FastForwardAction * - RewindAction * - ThumbsAction * - RepeatAction * - ShuffleAction * - HighQualityAction * - ClosedCaptioningAction */ notifyChanged(action); } } }); setAdapter(mRowsAdapter); } |
From here, Each action’s implementation is explained. Note that it is important to differentiate “UI update part” and “Video control part”, because Video control part will move to MediaSession in next chapter.
In the source code, I implemented “UI update part” in PlaybackOverlayFragment.java, while “Video control part” is implemented in PlaybackOverlayActivity.java.
PlayPauseAction
1 2 3 4 5 6 7 |
private void togglePlayback(boolean playPause) { /* Video control part */ ((PlaybackOverlayActivity) getActivity()).playPause(playPause); /* UI control part */ playbackStateChanged(); } |
Video control part will handle play/pause video in VideoView.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
public void playPause(boolean doPlay) { if (mPlaybackState == LeanbackPlaybackState.IDLE) { /* Callbacks for mVideoView */ setupCallbacks(); } if (doPlay && mPlaybackState != LeanbackPlaybackState.PLAYING) { mPlaybackState = LeanbackPlaybackState.PLAYING; if (mPosition > 0) { mVideoView.seekTo(mPosition); } mVideoView.start(); mStartTimeMillis = System.currentTimeMillis(); } else { mPlaybackState = LeanbackPlaybackState.PAUSED; int timeElapsedSinceStart = (int) (System.currentTimeMillis() - mStartTimeMillis); setPosition(mPosition + timeElapsedSinceStart); mVideoView.pause(); } } private void setupCallbacks() { mVideoView.setOnErrorListener(new MediaPlayer.OnErrorListener() { @Override public boolean onError(MediaPlayer mp, int what, int extra) { mVideoView.stopPlayback(); mPlaybackState = LeanbackPlaybackState.IDLE; return false; } }); mVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { @Override public void onPrepared(MediaPlayer mp) { if (mPlaybackState == LeanbackPlaybackState.PLAYING) { mVideoView.start(); } } }); mVideoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { @Override public void onCompletion(MediaPlayer mp) { mPlaybackState = LeanbackPlaybackState.IDLE; } }); } |
UI control part will handle
- Toggling icon of Play/Pause
- Update current time of video
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
public void playbackStateChanged() { if (mCurrentPlaybackState != PlaybackState.STATE_PLAYING) { mCurrentPlaybackState = PlaybackState.STATE_PLAYING; startProgressAutomation(); setFadingEnabled(true); mPlayPauseAction.setIndex(PlaybackControlsRow.PlayPauseAction.PAUSE); mPlayPauseAction.setIcon(mPlayPauseAction.getDrawable(PlaybackControlsRow.PlayPauseAction.PAUSE)); notifyChanged(mPlayPauseAction); } else if (mCurrentPlaybackState != PlaybackState.STATE_PAUSED) { mCurrentPlaybackState = PlaybackState.STATE_PAUSED; stopProgressAutomation(); //setFadingEnabled(false); // if set to false, PlaybackcontrolsRow will always be on the screen mPlayPauseAction.setIndex(PlaybackControlsRow.PlayPauseAction.PLAY); mPlayPauseAction.setIcon(mPlayPauseAction.getDrawable(PlaybackControlsRow.PlayPauseAction.PLAY)); notifyChanged(mPlayPauseAction); } int currentTime = ((PlaybackOverlayActivity) getActivity()).getPosition(); mPlaybackControlsRow.setCurrentTime(currentTime); mPlaybackControlsRow.setBufferedProgress(currentTime + SIMULATED_BUFFERED_TIME); } private void startProgressAutomation() { if (mRunnable == null) { mRunnable = new Runnable() { @Override public void run() { int updatePeriod = getUpdatePeriod(); int currentTime = mPlaybackControlsRow.getCurrentTime() + updatePeriod; int totalTime = mPlaybackControlsRow.getTotalTime(); mPlaybackControlsRow.setCurrentTime(currentTime); mPlaybackControlsRow.setBufferedProgress(currentTime + SIMULATED_BUFFERED_TIME); if (totalTime > 0 && totalTime <= currentTime) { stopProgressAutomation(); //next(true); } else { mHandler.postDelayed(this, updatePeriod); } } }; mHandler.postDelayed(mRunnable, getUpdatePeriod()); } } private void stopProgressAutomation() { if (mHandler != null && mRunnable != null) { mHandler.removeCallbacks(mRunnable); mRunnable = null; } } |
Rewind & FastForward
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
private void fastForward() { /* Video control part */ ((PlaybackOverlayActivity) getActivity()).fastForward(); /* UI part */ int currentTime = ((PlaybackOverlayActivity) getActivity()).getPosition(); mPlaybackControlsRow.setCurrentTime(currentTime); mPlaybackControlsRow.setBufferedProgress(currentTime + SIMULATED_BUFFERED_TIME); } private void rewind() { /* Video control part */ ((PlaybackOverlayActivity) getActivity()).rewind(); /* UI part */ int currentTime = ((PlaybackOverlayActivity) getActivity()).getPosition(); mPlaybackControlsRow.setCurrentTime(currentTime); mPlaybackControlsRow.setBufferedProgress(currentTime + SIMULATED_BUFFERED_TIME); } |
Here, rewind & fast forward Video control implementations are done in easy way, just rewind/fast forward 10 seconds from current position.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public void fastForward() { if (mDuration != -1) { // Fast forward 10 seconds. setPosition(mVideoView.getCurrentPosition() + (10 * 1000)); mVideoView.seekTo(mPosition); } } public void rewind() { // rewind 10 seconds setPosition(mVideoView.getCurrentPosition() - (10 * 1000)); mVideoView.seekTo(mPosition); } |
UI control part is updating current time of video.
SkipPrevious & SkipNext
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
private void next(boolean autoPlay) { /* Video control part */ if (++mCurrentItem >= mItems.size()) { // Current Item is set to next here mCurrentItem = 0; } if (autoPlay) { mCurrentPlaybackState = PlaybackState.STATE_PAUSED; } Movie movie = mItems.get(mCurrentItem); if (movie != null) { ((PlaybackOverlayActivity) getActivity()).setVideoPath(movie.getVideoUrl()); ((PlaybackOverlayActivity) getActivity()).setPlaybackState(PlaybackOverlayActivity.LeanbackPlaybackState.PAUSED); ((PlaybackOverlayActivity) getActivity()).playPause(autoPlay); } /* UI part */ playbackStateChanged(); updatePlaybackRow(mCurrentItem); } private void prev(boolean autoPlay) { /* Video control part */ if (--mCurrentItem < 0) { // Current Item is set to previous here mCurrentItem = mItems.size() - 1; } if (autoPlay) { mCurrentPlaybackState = PlaybackState.STATE_PAUSED; } Movie movie = mItems.get(mCurrentItem); if (movie != null) { ((PlaybackOverlayActivity) getActivity()).setVideoPath(movie.getVideoUrl()); ((PlaybackOverlayActivity) getActivity()).setPlaybackState(PlaybackOverlayActivity.LeanbackPlaybackState.PAUSED); ((PlaybackOverlayActivity) getActivity()).playPause(autoPlay); } /* UI part */ playbackStateChanged(); updatePlaybackRow(mCurrentItem); } |
For Video control part, 2 functions are doing same thing except for first line. mCurrentItem is set to previous/next followed by setting proper video path by using setVideoPath method & play/pause depending on current play/pause status by using playPause method.
UI control part, first line calls playbackStateChanged() method, but it is only necessary to control startProgressAutomation/stopProgressAutomation to update current time status of video. updateplaybackRow method is to update DetailsDescription information of video content.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
private void updatePlaybackRow(int index) { Log.d(TAG, "updatePlaybackRow"); if (mPlaybackControlsRow.getItem() != null) { Movie item = (Movie) mPlaybackControlsRow.getItem(); item.setTitle(mItems.get(mCurrentItem).getTitle()); item.setStudio(mItems.get(mCurrentItem).getStudio()); mRowsAdapter.notifyArrayItemRangeChanged(0, 1); /* total time is necessary to show video playing time progress bar */ int duration = (int) Utils.getDuration(mItems.get(mCurrentItem).getVideoUrl()); Log.i(TAG, "videoUrl: " + mItems.get(mCurrentItem).getVideoUrl()); Log.i(TAG, "duration = " + duration); mPlaybackControlsRow.setTotalTime(duration); mPlaybackControlsRow.setCurrentTime(0); mPlaybackControlsRow.setBufferedProgress(0); } if (SHOW_IMAGE) { mPlaybackControlsRowTarget = new PicassoPlaybackControlsRowTarget(mPlaybackControlsRow); updateVideoImage(mItems.get(mCurrentItem).getCardImageURI()); } } |
ThumbUp & ThumbDown & Repeat & Shuffle & HighQuality & ClosedCaptioning & MoreActions
How to toggle icon’s color? You can change by setting index of the action. Implement below in onActionClicked method to check how each index setting will behave.
1 2 3 4 5 6 7 8 9 |
/* Change icon */ if (action instanceof PlaybackControlsRow.ThumbsUpAction || action instanceof PlaybackControlsRow.ThumbsDownAction || action instanceof PlaybackControlsRow.RepeatAction || action instanceof PlaybackControlsRow.ShuffleAction || action instanceof PlaybackControlsRow.HighQualityAction || action instanceof PlaybackControlsRow.ClosedCaptioningAction) { ((PlaybackControlsRow.MultiAction) action).nextIndex(); } |

Action icon change in SecondaryActionsAdapter.
Build & Run
Now you can check that Video control is working correctly for PrimaryRow.
*SecondaryRow’s action implementation may differ depending on your desire and I will skip here.
* Note. I could take VideoView’s image by screen recording, but I couldn’t take VideoView’s screen capture via Android studio’s debugging tool… (I’m using Sony Android TV for development of Android TV now.)
Again source code is on github.
Next chapter, we implement MediaSession.
Hi, thank you for these tutorials.
Wanted to know if you ever solved video scale (zoom) task on Android TV? VideoView does not have these features… I’m trying to make something like “ORIGINAL”, “STRETCH”, “FILL” video modes. No luck yet…
Hi Denis, thanks for the comment.
I’m sorry that I haven’t tested yet. When I check on the web a little bit, I found Does Android support scaling Video?. Did you try overriding
onMeasure
method?Is there a way to play a youtube video with this embedded player??
I guess the correct way to achieve this is to use YouTube API.
There is a YouTube Android Player API, which you can get information at this official page: https://developers.google.com/youtube/android/player/sample-applications.
I haven’t tested these API yet, but I’m also interested in playing video on YouTube. So if I could manage time, I will update information by writing new post.
I was able to take your application and get a YouTube Video working in it!!
I changed the play video function to call the youtube video by using the YouTube API.
Hi, Good to know that. Actually I also tried YouTube API and summarized the information at Testing YouTube Android Player API on Android phone & Android TV.
Also, you can refer Seikeidenron-AndroidTV for implementation of this YouTube API on real App!
Hi corochann,
Thanks to write about Android TV media player, but code you shared is not playing following urls. Could you please help me.
http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4
http://commondatastorage.googleapis.com/android-tv/Sample%20videos/Zeitgeist/Zeitgeist%202010_%20Year%20in%20Review.mp4
I have tried your code with media session. Code running with ‘m3u8’ formate.
Hi Shivang,
So far I have no idea for this bug,,, can you share me the error log(logcat log)?
Hi. Correct a bit your sample: un Playback Activity method setPosition(int) is missed.
Thank you for comment. If you found some bugs at latest code on github, feel free to send me a pull request as well!
Hi,
I was trying to implement this menu with exo player but with no luck at all 🙁 Could you give me a little lead how would you do it ? I have problem with layouting since exo player needs to have subbtitle layout etc.
Thx
There is small bug in PlaybackOverlayFragment. When you click on action in secondary row its ignored for first time. Its caused because notifyChanged(action); is called before ((PlaybackControlsRow.MultiAction) action).nextIndex();
It can be fixed simply by moving notifyChanged(action); under if statement like this :
if (action instanceof PlaybackControlsRow.ThumbsUpAction ||
action instanceof PlaybackControlsRow.ThumbsDownAction ||
action instanceof PlaybackControlsRow.RepeatAction ||
action instanceof PlaybackControlsRow.ShuffleAction ||
action instanceof PlaybackControlsRow.HighQualityAction ||
action instanceof PlaybackControlsRow.ClosedCaptioningAction) {
((PlaybackControlsRow.MultiAction) action).nextIndex();
}
notifyChanged(action);
Hi,
Thank you Yuck for your bug fix comment! I will update it later.
And sorry that I have not tested exo player before, could you ask stackoverflow for it?
HI,
I have been regularly following you blog, and would like to thank you for posting and knowledge sharing.
I am trying to create a streaming application using the Leanback library for the android (Kitkat Verison) STB. The chipset is AMlogic S805. I tweaked a little in the gradle with the minSDkverison and TargetVerison and the Leanback UI is supported, the DetailActivity, PlaybackOverlay etc is working fine. The only problem is with the Player. I tried to use your sample app on the device and found that it doesnt support the playback. However I was able to play it on the AndroidTV. Can you suggest any tweaks that can be done in order to play the video for the Kitkat version. I mean if any other player you would recommed to be used.
Thanks again for sharing your knowledge.
Regards,
Hi,
Thanks for your comment. As far as I know,
MediaSession
&PlaybackState
&MediaMetadata
are supported from API level 21 (Android TV is introduced from API level 21). So you need to modify this dependency.To make it work under API level 21, one way is to remove related codes for above.
Another way is to try using
MediaSessionCompat
&PlaybackStateCompat
&MediaMetadataCompat
, but I have not tested them before.Thanks
Hi,
Any idea how to do continuous seeking on pressing of forward and rewind button rather than just jumping for a fixed time.
Hi Corochann,
Can you please guide on how to display Closed Captions given we have a srt file.
TIA
Hi Cocrochan!n!!
I love this tutorial!
I am having an issue with the folowing:
E/AndroidRuntime: FATAL EXCEPTION: main Process: software.blackstone.sunnahstreamtv, PID: 2321 java.lang.RuntimeException: Unable to start activity ComponentInfo{software.blackstone.sunnahstreamtv/software.blackstone.sunnahstreamtv.PlaybackOverlayActivity}: android.view.InflateException: Binary XML file line #13: Binary XML file line #13: Error inflating class fragment at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2416) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2476) at android.app.ActivityThread.-wrap11(ActivityThread.java) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1344) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:148) at android.app.ActivityThread.main(ActivityThread.java:5417) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616) Caused by: android.view.InflateException: Binary XML file line #13: Binary XML file line #13: Error inflating class fragment at android.view.LayoutInflater.inflate(LayoutInflater.java:539) at android.view.LayoutInflater.inflate(LayoutInflater.java:423) at android.view.LayoutInflater.inflate(LayoutInflater.java:374) at com.android.internal.policy.PhoneWindow.setContentView(PhoneWindow.java:393) at android.app.Activity.setContentView(Activity.java:2166) at software.blackstone.sunnahstreamtv.PlaybackOverlayActivity.onCreate(PlaybackOverlayActivity.java:35) at android.app.Activity.performCreate(Activity.java:6237) at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1107) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2369) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2476) at android.app.ActivityThread.-wrap11(ActivityThread.java) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1344) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:148) at android.app.ActivityThread.main(ActivityThread.java:5417) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616) Caused by: android.view.InflateException: Binary XML file line #13: Error inflating class fragment at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:782) at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:704) at android.view.LayoutInflater.rInflate(LayoutInflater.java:835) at android.view.LayoutInflater.rInflateChildren(LayoutInflater.java:798) at android.view.LayoutInflater.inflate(LayoutInflater.java:515) at android.view.LayoutInflater.inflate(LayoutInflater.java:423) at android.view.LayoutInflater.inflate(LayoutInflater.java:374) at com.android.internal.policy.PhoneWindow.setContentView(PhoneWindow.java:393) at android.app.Activity.setContentView(Activity.java:2166) at software.blackstone.sunnahstreamtv.PlaybackOverlayActivity.onCreate(PlaybackOverlayActivity.java:35) at android.app.Activity.performCreate(Activity.java:6237) at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1107) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2369) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2476) at android.app.ActivityThread.-wrap11(ActivityThread.java) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1344) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:148) at android.app.ActivityThread.main(ActivityThread.java:5417) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616) Caused by: java.lang.ArrayIndexOutOfBoundsException: length=12; index=-1 at java.util.ArrayList.get(ArrayList.java:310) at software.blackstone.sunnahstreamtv.PlaybackOverlayFragment.updatePlaybackRow(PlaybackOverlayFragment.java:364) at software.blackstone.sunnahstreamtv.PlaybackOverlayFragment.addPlaybackControlsRow(PlaybackOverlayFragment.java:347) at software.blackstone.sunnahstreamtv.PlaybackOverlayFragment.setUpRows(PlaybackOverlayFragment.java:109) at software.blackstone.sunnahstreamtv.PlaybackOverlayFragment.onCreate(PlaybackOverlayFragment.java:89) at android.app.Fragment.performCreate(Fragment.java:2198) at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:942) at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:1126) at android.app.FragmentManagerImpl.addFragment(FragmentManager.java:1228) at android.app.FragmentManagerImpl.onCreateView(FragmentManager.java:2201) at android.app.FragmentController.onCreateView(FragmentController.java:98) at android.app.Activity.onCreateView(Activity.java:5546) at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:754) at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:704) at android.view.LayoutInflater.rInflate(LayoutInflater.java:835) at android.view.LayoutInflater.rInflateChildren(LayoutInflater.java:798) at android.view.LayoutInflater.inflate(LayoutInflater.java:515) at android.view.LayoutInflater.inflate(LayoutInflater.java:423) at android.view.LayoutInflater.inflate(LayoutInflater.java:374) at com.android.internal.policy.PhoneWindow.setContentView(PhoneWindow.java:393) at android.app.Activity.setContentView(Activity.java:2166) at software.blackstone.sunnahstreamtv.PlaybackOverlayActivity.onCreate(PlaybackOverlayActivity.java:35) at android.app.Activity.performCreate(Activity.java:6237) at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1107) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2369) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2476) at android.app.ActivityThread.-wrap11(ActivityThread.java) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1344) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:148) at android.app.ActivityThread.main(ActivityThread.java:5417) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
I have no idea what this error is.
Can you help me?