Merge "Improve TransportController demo." into jb-mr2-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
a5e017432a
@@ -167,7 +167,7 @@ public class VideoPlayerActivity extends Activity
|
|||||||
h.removeCallbacks(mNavHider);
|
h.removeCallbacks(mNavHider);
|
||||||
if (!mMenusOpen && !mPaused) {
|
if (!mMenusOpen && !mPaused) {
|
||||||
// If the menus are open or play is paused, we will not auto-hide.
|
// If the menus are open or play is paused, we will not auto-hide.
|
||||||
h.postDelayed(mNavHider, 1500);
|
h.postDelayed(mNavHider, 3000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
76
samples/Support4Demos/res/layout/media_controller.xml
Normal file
76
samples/Support4Demos/res/layout/media_controller.xml
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Copyright (C) 2007 The Android Open Source Project
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="#CC000000"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layoutDirection="ltr">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:paddingTop="4dip"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<ImageButton android:id="@+id/prev" style="@android:style/MediaButton.Previous" />
|
||||||
|
<ImageButton android:id="@+id/rew" style="@android:style/MediaButton.Rew" />
|
||||||
|
<ImageButton android:id="@+id/pause" style="@android:style/MediaButton.Play" />
|
||||||
|
<ImageButton android:id="@+id/ffwd" style="@android:style/MediaButton.Ffwd" />
|
||||||
|
<ImageButton android:id="@+id/next" style="@android:style/MediaButton.Next" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView android:id="@+id/time_current"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:paddingTop="4dip"
|
||||||
|
android:paddingStart="4dip"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingEnd="4dip"
|
||||||
|
android:textColor="?android:attr/textColorSecondary" />
|
||||||
|
|
||||||
|
<SeekBar
|
||||||
|
android:id="@+id/mediacontroller_progress"
|
||||||
|
style="?android:attr/progressBarStyleHorizontal"
|
||||||
|
android:layout_width="0dip"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_height="32dip"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
android:layout_alignParentEnd="true" />
|
||||||
|
|
||||||
|
<TextView android:id="@+id/time"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:paddingTop="4dip"
|
||||||
|
android:paddingEnd="4dip"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingStart="4dip"
|
||||||
|
android:textColor="?android:attr/textColorSecondary" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@@ -1,14 +1,39 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<!-- Copyright (C) 2012 The Android Open Source Project
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
>
|
|
||||||
|
|
||||||
<VideoView
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
android:id="@+id/surface_view"
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent" android:layout_height="match_parent"
|
||||||
|
>
|
||||||
|
<view class="com.example.android.supportv4.media.TransportControllerActivity$Content"
|
||||||
|
android:id="@+id/content"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
/>
|
/>
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:fitsSystemWindows="true"
|
||||||
|
android:animateLayoutChanges="true"
|
||||||
|
>
|
||||||
|
<com.example.android.supportv4.media.MediaController
|
||||||
|
android:id="@+id/media_controller"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="bottom">
|
||||||
|
</com.example.android.supportv4.media.MediaController>
|
||||||
|
</FrameLayout>
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|||||||
@@ -0,0 +1,360 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2013 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.example.android.supportv4.media;
|
||||||
|
|
||||||
|
import com.example.android.supportv4.R;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.accessibility.AccessibilityEvent;
|
||||||
|
import android.view.accessibility.AccessibilityNodeInfo;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
import android.widget.ImageButton;
|
||||||
|
import android.widget.ProgressBar;
|
||||||
|
import android.widget.SeekBar;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import java.util.Formatter;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper for implementing media controls in an application.
|
||||||
|
* Use instead of the very useful android.widget.MediaController.
|
||||||
|
* This version is embedded inside of an application's layout.
|
||||||
|
*/
|
||||||
|
public class MediaController extends FrameLayout {
|
||||||
|
|
||||||
|
private MediaPlayerControl mPlayer;
|
||||||
|
private Context mContext;
|
||||||
|
private ProgressBar mProgress;
|
||||||
|
private TextView mEndTime, mCurrentTime;
|
||||||
|
private boolean mDragging;
|
||||||
|
private boolean mUseFastForward;
|
||||||
|
private boolean mFromXml;
|
||||||
|
private boolean mListenersSet;
|
||||||
|
private View.OnClickListener mNextListener, mPrevListener;
|
||||||
|
StringBuilder mFormatBuilder;
|
||||||
|
Formatter mFormatter;
|
||||||
|
private ImageButton mPauseButton;
|
||||||
|
private ImageButton mFfwdButton;
|
||||||
|
private ImageButton mRewButton;
|
||||||
|
private ImageButton mNextButton;
|
||||||
|
private ImageButton mPrevButton;
|
||||||
|
|
||||||
|
public MediaController(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
mContext = context;
|
||||||
|
mUseFastForward = true;
|
||||||
|
mFromXml = true;
|
||||||
|
LayoutInflater inflate = (LayoutInflater)
|
||||||
|
mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||||
|
inflate.inflate(R.layout.media_controller, this, true);
|
||||||
|
initControllerView();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MediaController(Context context, boolean useFastForward) {
|
||||||
|
super(context);
|
||||||
|
mContext = context;
|
||||||
|
mUseFastForward = useFastForward;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MediaController(Context context) {
|
||||||
|
this(context, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMediaPlayer(MediaPlayerControl player) {
|
||||||
|
mPlayer = player;
|
||||||
|
updatePausePlay();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initControllerView() {
|
||||||
|
mPauseButton = (ImageButton) findViewById(R.id.pause);
|
||||||
|
if (mPauseButton != null) {
|
||||||
|
mPauseButton.requestFocus();
|
||||||
|
mPauseButton.setOnClickListener(mPauseListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
mFfwdButton = (ImageButton) findViewById(R.id.ffwd);
|
||||||
|
if (mFfwdButton != null) {
|
||||||
|
mFfwdButton.setOnClickListener(mFfwdListener);
|
||||||
|
if (!mFromXml) {
|
||||||
|
mFfwdButton.setVisibility(mUseFastForward ? View.VISIBLE : View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mRewButton = (ImageButton) findViewById(R.id.rew);
|
||||||
|
if (mRewButton != null) {
|
||||||
|
mRewButton.setOnClickListener(mRewListener);
|
||||||
|
if (!mFromXml) {
|
||||||
|
mRewButton.setVisibility(mUseFastForward ? View.VISIBLE : View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// By default these are hidden. They will be enabled when setPrevNextListeners() is called
|
||||||
|
mNextButton = (ImageButton) findViewById(R.id.next);
|
||||||
|
if (mNextButton != null && !mFromXml && !mListenersSet) {
|
||||||
|
mNextButton.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
mPrevButton = (ImageButton) findViewById(R.id.prev);
|
||||||
|
if (mPrevButton != null && !mFromXml && !mListenersSet) {
|
||||||
|
mPrevButton.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
mProgress = (ProgressBar) findViewById(R.id.mediacontroller_progress);
|
||||||
|
if (mProgress != null) {
|
||||||
|
if (mProgress instanceof SeekBar) {
|
||||||
|
SeekBar seeker = (SeekBar) mProgress;
|
||||||
|
seeker.setOnSeekBarChangeListener(mSeekListener);
|
||||||
|
}
|
||||||
|
mProgress.setMax(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
mEndTime = (TextView) findViewById(R.id.time);
|
||||||
|
mCurrentTime = (TextView) findViewById(R.id.time_current);
|
||||||
|
mFormatBuilder = new StringBuilder();
|
||||||
|
mFormatter = new Formatter(mFormatBuilder, Locale.getDefault());
|
||||||
|
|
||||||
|
installPrevNextListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disable pause or seek buttons if the stream cannot be paused or seeked.
|
||||||
|
* This requires the control interface to be a MediaPlayerControlExt
|
||||||
|
*/
|
||||||
|
private void disableUnsupportedButtons() {
|
||||||
|
try {
|
||||||
|
if (mPauseButton != null && !mPlayer.canPause()) {
|
||||||
|
mPauseButton.setEnabled(false);
|
||||||
|
}
|
||||||
|
if (mRewButton != null && !mPlayer.canSeekBackward()) {
|
||||||
|
mRewButton.setEnabled(false);
|
||||||
|
}
|
||||||
|
if (mFfwdButton != null && !mPlayer.canSeekForward()) {
|
||||||
|
mFfwdButton.setEnabled(false);
|
||||||
|
}
|
||||||
|
} catch (IncompatibleClassChangeError ex) {
|
||||||
|
// We were given an old version of the interface, that doesn't have
|
||||||
|
// the canPause/canSeekXYZ methods. This is OK, it just means we
|
||||||
|
// assume the media can be paused and seeked, and so we don't disable
|
||||||
|
// the buttons.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void refresh() {
|
||||||
|
updateProgress();
|
||||||
|
disableUnsupportedButtons();
|
||||||
|
updatePausePlay();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String stringForTime(int timeMs) {
|
||||||
|
int totalSeconds = timeMs / 1000;
|
||||||
|
|
||||||
|
int seconds = totalSeconds % 60;
|
||||||
|
int minutes = (totalSeconds / 60) % 60;
|
||||||
|
int hours = totalSeconds / 3600;
|
||||||
|
|
||||||
|
mFormatBuilder.setLength(0);
|
||||||
|
if (hours > 0) {
|
||||||
|
return mFormatter.format("%d:%02d:%02d", hours, minutes, seconds).toString();
|
||||||
|
} else {
|
||||||
|
return mFormatter.format("%02d:%02d", minutes, seconds).toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int updateProgress() {
|
||||||
|
if (mPlayer == null || mDragging) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int position = mPlayer.getCurrentPosition();
|
||||||
|
int duration = mPlayer.getDuration();
|
||||||
|
if (mProgress != null) {
|
||||||
|
if (duration > 0) {
|
||||||
|
// use long to avoid overflow
|
||||||
|
long pos = 1000L * position / duration;
|
||||||
|
mProgress.setProgress( (int) pos);
|
||||||
|
}
|
||||||
|
int percent = mPlayer.getBufferPercentage();
|
||||||
|
mProgress.setSecondaryProgress(percent * 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mEndTime != null)
|
||||||
|
mEndTime.setText(stringForTime(duration));
|
||||||
|
if (mCurrentTime != null)
|
||||||
|
mCurrentTime.setText(stringForTime(position));
|
||||||
|
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
private View.OnClickListener mPauseListener = new View.OnClickListener() {
|
||||||
|
public void onClick(View v) {
|
||||||
|
doPauseResume();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private void updatePausePlay() {
|
||||||
|
if (mPauseButton == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (mPlayer.isPlaying()) {
|
||||||
|
mPauseButton.setImageResource(android.R.drawable.ic_media_pause);
|
||||||
|
} else {
|
||||||
|
mPauseButton.setImageResource(android.R.drawable.ic_media_play);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doPauseResume() {
|
||||||
|
if (mPlayer.isPlaying()) {
|
||||||
|
mPlayer.pause();
|
||||||
|
} else {
|
||||||
|
mPlayer.start();
|
||||||
|
}
|
||||||
|
updatePausePlay();
|
||||||
|
}
|
||||||
|
|
||||||
|
// There are two scenarios that can trigger the seekbar listener to trigger:
|
||||||
|
//
|
||||||
|
// The first is the user using the touchpad to adjust the posititon of the
|
||||||
|
// seekbar's thumb. In this case onStartTrackingTouch is called followed by
|
||||||
|
// a number of onProgressChanged notifications, concluded by onStopTrackingTouch.
|
||||||
|
// We're setting the field "mDragging" to true for the duration of the dragging
|
||||||
|
// session to avoid jumps in the position in case of ongoing playback.
|
||||||
|
//
|
||||||
|
// The second scenario involves the user operating the scroll ball, in this
|
||||||
|
// case there WON'T BE onStartTrackingTouch/onStopTrackingTouch notifications,
|
||||||
|
// we will simply apply the updated position without suspending regular updates.
|
||||||
|
private SeekBar.OnSeekBarChangeListener mSeekListener = new SeekBar.OnSeekBarChangeListener() {
|
||||||
|
public void onStartTrackingTouch(SeekBar bar) {
|
||||||
|
mDragging = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onProgressChanged(SeekBar bar, int progress, boolean fromuser) {
|
||||||
|
if (!fromuser) {
|
||||||
|
// We're not interested in programmatically generated changes to
|
||||||
|
// the progress bar's position.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
long duration = mPlayer.getDuration();
|
||||||
|
long newposition = (duration * progress) / 1000L;
|
||||||
|
mPlayer.seekTo( (int) newposition);
|
||||||
|
if (mCurrentTime != null)
|
||||||
|
mCurrentTime.setText(stringForTime( (int) newposition));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onStopTrackingTouch(SeekBar bar) {
|
||||||
|
mDragging = false;
|
||||||
|
updateProgress();
|
||||||
|
updatePausePlay();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEnabled(boolean enabled) {
|
||||||
|
if (mPauseButton != null) {
|
||||||
|
mPauseButton.setEnabled(enabled);
|
||||||
|
}
|
||||||
|
if (mFfwdButton != null) {
|
||||||
|
mFfwdButton.setEnabled(enabled);
|
||||||
|
}
|
||||||
|
if (mRewButton != null) {
|
||||||
|
mRewButton.setEnabled(enabled);
|
||||||
|
}
|
||||||
|
if (mNextButton != null) {
|
||||||
|
mNextButton.setEnabled(enabled && mNextListener != null);
|
||||||
|
}
|
||||||
|
if (mPrevButton != null) {
|
||||||
|
mPrevButton.setEnabled(enabled && mPrevListener != null);
|
||||||
|
}
|
||||||
|
if (mProgress != null) {
|
||||||
|
mProgress.setEnabled(enabled);
|
||||||
|
}
|
||||||
|
disableUnsupportedButtons();
|
||||||
|
super.setEnabled(enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
|
||||||
|
super.onInitializeAccessibilityEvent(event);
|
||||||
|
event.setClassName(MediaController.class.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
|
||||||
|
super.onInitializeAccessibilityNodeInfo(info);
|
||||||
|
info.setClassName(MediaController.class.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
private View.OnClickListener mRewListener = new View.OnClickListener() {
|
||||||
|
public void onClick(View v) {
|
||||||
|
int pos = mPlayer.getCurrentPosition();
|
||||||
|
pos -= 5000; // milliseconds
|
||||||
|
mPlayer.seekTo(pos);
|
||||||
|
updateProgress();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private View.OnClickListener mFfwdListener = new View.OnClickListener() {
|
||||||
|
public void onClick(View v) {
|
||||||
|
int pos = mPlayer.getCurrentPosition();
|
||||||
|
pos += 15000; // milliseconds
|
||||||
|
mPlayer.seekTo(pos);
|
||||||
|
updateProgress();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private void installPrevNextListeners() {
|
||||||
|
if (mNextButton != null) {
|
||||||
|
mNextButton.setOnClickListener(mNextListener);
|
||||||
|
mNextButton.setEnabled(mNextListener != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mPrevButton != null) {
|
||||||
|
mPrevButton.setOnClickListener(mPrevListener);
|
||||||
|
mPrevButton.setEnabled(mPrevListener != null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPrevNextListeners(View.OnClickListener next, View.OnClickListener prev) {
|
||||||
|
mNextListener = next;
|
||||||
|
mPrevListener = prev;
|
||||||
|
mListenersSet = true;
|
||||||
|
|
||||||
|
installPrevNextListeners();
|
||||||
|
|
||||||
|
if (mNextButton != null && !mFromXml) {
|
||||||
|
mNextButton.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
if (mPrevButton != null && !mFromXml) {
|
||||||
|
mPrevButton.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface MediaPlayerControl {
|
||||||
|
void start();
|
||||||
|
void pause();
|
||||||
|
int getDuration();
|
||||||
|
int getCurrentPosition();
|
||||||
|
void seekTo(int pos);
|
||||||
|
boolean isPlaying();
|
||||||
|
int getBufferPercentage();
|
||||||
|
boolean canPause();
|
||||||
|
boolean canSeekBackward();
|
||||||
|
boolean canSeekForward();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,13 +16,19 @@
|
|||||||
|
|
||||||
package com.example.android.supportv4.media;
|
package com.example.android.supportv4.media;
|
||||||
|
|
||||||
import android.view.KeyEvent;
|
|
||||||
import com.example.android.supportv4.R;
|
import com.example.android.supportv4.R;
|
||||||
|
|
||||||
|
import android.app.ActionBar;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.media.MediaPlayer;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.widget.MediaController;
|
|
||||||
import android.widget.VideoView;
|
import android.widget.VideoView;
|
||||||
|
|
||||||
import android.support.v4.media.TransportController;
|
import android.support.v4.media.TransportController;
|
||||||
@@ -33,33 +39,266 @@ public class TransportControllerActivity extends Activity {
|
|||||||
* TODO: Set the path variable to a streaming video URL or a local media
|
* TODO: Set the path variable to a streaming video URL or a local media
|
||||||
* file path.
|
* file path.
|
||||||
*/
|
*/
|
||||||
private VideoView mVideoView;
|
private Content mContent;
|
||||||
private TransportController mTransportController;
|
private TransportController mTransportController;
|
||||||
|
private MediaController mMediaController;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle media buttons to start/stop video playback. Real implementations
|
* Handle media buttons to start/stop video playback. Real implementations
|
||||||
* will probably handle more buttons, like skip and fast-forward.
|
* will probably handle more buttons, like skip and fast-forward.
|
||||||
*/
|
*/
|
||||||
public class PlayerControlCallbacks extends TransportController.Callbacks {
|
TransportController.Callbacks mTransportCallbacks = new TransportController.Callbacks() {
|
||||||
public boolean onMediaButtonDown(int keyCode, KeyEvent event) {
|
public boolean onMediaButtonDown(int keyCode, KeyEvent event) {
|
||||||
switch (keyCode) {
|
switch (keyCode) {
|
||||||
case TransportController.KEYCODE_MEDIA_PLAY:
|
case TransportController.KEYCODE_MEDIA_PLAY:
|
||||||
mVideoView.start();
|
mMediaPlayerControl.start();
|
||||||
return true;
|
return true;
|
||||||
case TransportController.KEYCODE_MEDIA_PAUSE:
|
case TransportController.KEYCODE_MEDIA_PAUSE:
|
||||||
case KeyEvent.KEYCODE_MEDIA_STOP:
|
case KeyEvent.KEYCODE_MEDIA_STOP:
|
||||||
mVideoView.pause();
|
mMediaPlayerControl.pause();
|
||||||
return true;
|
return true;
|
||||||
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
|
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
|
||||||
case KeyEvent.KEYCODE_HEADSETHOOK:
|
case KeyEvent.KEYCODE_HEADSETHOOK:
|
||||||
if (mVideoView.isPlaying()) {
|
if (mContent.isPlaying()) {
|
||||||
mVideoView.pause();
|
mMediaPlayerControl.pause();
|
||||||
} else {
|
} else {
|
||||||
mVideoView.start();
|
mMediaPlayerControl.start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle actions from on-screen media controls. Most of these are simple re-directs
|
||||||
|
* to the VideoView; some we need to capture to update our state.
|
||||||
|
*/
|
||||||
|
MediaController.MediaPlayerControl mMediaPlayerControl
|
||||||
|
= new MediaController.MediaPlayerControl() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start() {
|
||||||
|
mTransportController.startPlaying();
|
||||||
|
mContent.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void pause() {
|
||||||
|
mTransportController.pausePlaying();
|
||||||
|
mContent.pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getDuration() {
|
||||||
|
return mContent.getDuration();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCurrentPosition() {
|
||||||
|
return mContent.getCurrentPosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void seekTo(int pos) {
|
||||||
|
mContent.seekTo(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isPlaying() {
|
||||||
|
return mContent.isPlaying();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getBufferPercentage() {
|
||||||
|
return mContent.getBufferPercentage();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canPause() {
|
||||||
|
return mContent.canPause();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canSeekBackward() {
|
||||||
|
return mContent.canSeekBackward();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canSeekForward() {
|
||||||
|
return mContent.canSeekForward();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the actual video player. It is the top-level content of
|
||||||
|
* the activity's view hierarchy, going under the status bar and nav
|
||||||
|
* bar areas.
|
||||||
|
*/
|
||||||
|
public static class Content extends VideoView implements
|
||||||
|
View.OnSystemUiVisibilityChangeListener, View.OnClickListener,
|
||||||
|
ActionBar.OnMenuVisibilityListener, MediaPlayer.OnPreparedListener,
|
||||||
|
MediaPlayer.OnCompletionListener, MediaPlayer.OnErrorListener {
|
||||||
|
Activity mActivity;
|
||||||
|
TransportController mTransportController;
|
||||||
|
MediaController mMediaController;
|
||||||
|
boolean mAddedMenuListener;
|
||||||
|
boolean mMenusOpen;
|
||||||
|
boolean mPaused;
|
||||||
|
boolean mNavVisible;
|
||||||
|
int mLastSystemUiVis;
|
||||||
|
|
||||||
|
Runnable mNavHider = new Runnable() {
|
||||||
|
@Override public void run() {
|
||||||
|
setNavVisibility(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Runnable mProgressUpdater = new Runnable() {
|
||||||
|
@Override public void run() {
|
||||||
|
mMediaController.updateProgress();
|
||||||
|
getHandler().postDelayed(this, 1000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public Content(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
setOnSystemUiVisibilityChangeListener(this);
|
||||||
|
setOnClickListener(this);
|
||||||
|
setOnPreparedListener(this);
|
||||||
|
setOnCompletionListener(this);
|
||||||
|
setOnErrorListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void init(Activity activity, TransportController transportController,
|
||||||
|
MediaController mediaController) {
|
||||||
|
// This called by the containing activity to supply the surrounding
|
||||||
|
// state of the video player that it will interact with.
|
||||||
|
mActivity = activity;
|
||||||
|
mTransportController = transportController;
|
||||||
|
mMediaController = mediaController;
|
||||||
|
pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override protected void onAttachedToWindow() {
|
||||||
|
super.onAttachedToWindow();
|
||||||
|
if (mActivity != null) {
|
||||||
|
mAddedMenuListener = true;
|
||||||
|
mActivity.getActionBar().addOnMenuVisibilityListener(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override protected void onDetachedFromWindow() {
|
||||||
|
super.onDetachedFromWindow();
|
||||||
|
if (mAddedMenuListener) {
|
||||||
|
mActivity.getActionBar().removeOnMenuVisibilityListener(this);
|
||||||
|
}
|
||||||
|
mNavVisible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void onSystemUiVisibilityChange(int visibility) {
|
||||||
|
// Detect when we go out of nav-hidden mode, to clear our state
|
||||||
|
// back to having the full UI chrome up. Only do this when
|
||||||
|
// the state is changing and nav is no longer hidden.
|
||||||
|
int diff = mLastSystemUiVis ^ visibility;
|
||||||
|
mLastSystemUiVis = visibility;
|
||||||
|
if ((diff&SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0
|
||||||
|
&& (visibility&SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0) {
|
||||||
|
setNavVisibility(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override protected void onWindowVisibilityChanged(int visibility) {
|
||||||
|
super.onWindowVisibilityChanged(visibility);
|
||||||
|
|
||||||
|
// When we become visible or invisible, play is paused.
|
||||||
|
pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void onClick(View v) {
|
||||||
|
// Clicking anywhere makes the navigation visible.
|
||||||
|
setNavVisibility(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void onMenuVisibilityChanged(boolean isVisible) {
|
||||||
|
mMenusOpen = isVisible;
|
||||||
|
setNavVisibility(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPrepared(MediaPlayer mp) {
|
||||||
|
mMediaController.setEnabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCompletion(MediaPlayer mp) {
|
||||||
|
mTransportController.pausePlaying();
|
||||||
|
pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onError(MediaPlayer mp, int what, int extra) {
|
||||||
|
mTransportController.pausePlaying();
|
||||||
|
pause();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void start() {
|
||||||
|
super.start();
|
||||||
|
mPaused = false;
|
||||||
|
setKeepScreenOn(true);
|
||||||
|
setNavVisibility(true);
|
||||||
|
mMediaController.refresh();
|
||||||
|
scheduleProgressUpdater();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void pause() {
|
||||||
|
super.pause();
|
||||||
|
mPaused = true;
|
||||||
|
setKeepScreenOn(false);
|
||||||
|
setNavVisibility(true);
|
||||||
|
mMediaController.refresh();
|
||||||
|
scheduleProgressUpdater();
|
||||||
|
}
|
||||||
|
|
||||||
|
void scheduleProgressUpdater() {
|
||||||
|
Handler h = getHandler();
|
||||||
|
if (h != null) {
|
||||||
|
if (mNavVisible && !mPaused) {
|
||||||
|
h.removeCallbacks(mProgressUpdater);
|
||||||
|
h.post(mProgressUpdater);
|
||||||
|
} else {
|
||||||
|
h.removeCallbacks(mProgressUpdater);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setNavVisibility(boolean visible) {
|
||||||
|
int newVis = SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||||
|
| SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||||
|
| SYSTEM_UI_FLAG_LAYOUT_STABLE;
|
||||||
|
if (!visible) {
|
||||||
|
newVis |= SYSTEM_UI_FLAG_LOW_PROFILE | SYSTEM_UI_FLAG_FULLSCREEN
|
||||||
|
| SYSTEM_UI_FLAG_HIDE_NAVIGATION;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we are now visible, schedule a timer for us to go invisible.
|
||||||
|
if (visible) {
|
||||||
|
Handler h = getHandler();
|
||||||
|
if (h != null) {
|
||||||
|
h.removeCallbacks(mNavHider);
|
||||||
|
if (!mMenusOpen && !mPaused) {
|
||||||
|
// If the menus are open or play is paused, we will not auto-hide.
|
||||||
|
h.postDelayed(mNavHider, 3000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the new desired visibility.
|
||||||
|
setSystemUiVisibility(newVis);
|
||||||
|
mNavVisible = visible;
|
||||||
|
mMediaController.setVisibility(visible ? VISIBLE : INVISIBLE);
|
||||||
|
scheduleProgressUpdater();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -68,27 +307,31 @@ public class TransportControllerActivity extends Activity {
|
|||||||
setContentView(R.layout.videoview);
|
setContentView(R.layout.videoview);
|
||||||
|
|
||||||
// Find the video player in our UI.
|
// Find the video player in our UI.
|
||||||
mVideoView = (VideoView) findViewById(R.id.surface_view);
|
mContent = (Content) findViewById(R.id.content);
|
||||||
|
|
||||||
// Create transport controller to control video; use the standard
|
// Create and initialize the media control UI.
|
||||||
// control callbacks that knows how to talk to a MediaPlayerControl.
|
mMediaController = (MediaController) findViewById(R.id.media_controller);
|
||||||
mTransportController = new TransportController(this, new PlayerControlCallbacks());
|
mMediaController.setMediaPlayer(mMediaPlayerControl);
|
||||||
|
|
||||||
|
// Create transport controller to control video, giving the callback
|
||||||
|
// interface to receive actions from.
|
||||||
|
mTransportController = new TransportController(this, mTransportCallbacks);
|
||||||
|
|
||||||
// We're just playing a built-in demo video.
|
// We're just playing a built-in demo video.
|
||||||
mVideoView.setVideoURI(Uri.parse("android.resource://" + getPackageName() +
|
mContent.init(this, mTransportController, mMediaController);
|
||||||
|
mContent.setVideoURI(Uri.parse("android.resource://" + getPackageName() +
|
||||||
"/" + R.raw.videoviewdemo));
|
"/" + R.raw.videoviewdemo));
|
||||||
mVideoView.setMediaController(new MediaController(this));
|
|
||||||
mVideoView.requestFocus();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean dispatchKeyEvent(KeyEvent event) {
|
public boolean dispatchKeyEvent(KeyEvent event) {
|
||||||
if (super.dispatchKeyEvent(event)) {
|
// We first dispatch keys to the transport controller -- we want it
|
||||||
|
// to get to consume any media keys rather than letting whoever has focus
|
||||||
|
// in the view hierarchy to potentially eat it.
|
||||||
|
if (mTransportController.dispatchKeyEvent(event)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the UI didn't handle the key, give the transport controller
|
return super.dispatchKeyEvent(event);
|
||||||
// a crack at it.
|
|
||||||
return mTransportController.dispatchKeyEvent(event);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user