diff options
Diffstat (limited to 'src/org/codeaurora/gallery3d/video')
9 files changed, 1997 insertions, 1 deletions
diff --git a/src/org/codeaurora/gallery3d/video/BookmarkActivity.java b/src/org/codeaurora/gallery3d/video/BookmarkActivity.java new file mode 100644 index 000000000..ed653f63a --- /dev/null +++ b/src/org/codeaurora/gallery3d/video/BookmarkActivity.java @@ -0,0 +1,244 @@ +package org.codeaurora.gallery3d.video; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; +import android.content.Intent; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; +import android.net.Uri; +import android.os.Bundle; +import android.util.Log; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.AdapterView; +import android.widget.AdapterView.AdapterContextMenuInfo; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.EditText; +import android.widget.ListView; +import android.widget.SimpleCursorAdapter; +import android.widget.TextView; + +import com.android.gallery3d.R; +import com.android.gallery3d.app.MovieActivity; + +public class BookmarkActivity extends Activity implements OnItemClickListener { + private static final String TAG = "BookmarkActivity"; + private static final boolean LOG = true; + + private BookmarkEnhance mBookmark; + private BookmarkAdapter mAdapter; + private Cursor mCursor; + private ListView mListView; + private TextView mEmptyView; + + private static final int MENU_DELETE_ALL = 1; + private static final int MENU_DELETE_ONE = 2; + private static final int MENU_EDIT = 3; + + public static final String KEY_LOGO_BITMAP = "logo-bitmap"; + + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.bookmark); + + Bitmap logo = getIntent().getParcelableExtra(KEY_LOGO_BITMAP); + if (logo != null) { + getActionBar().setLogo(new BitmapDrawable(getResources(), logo)); + } + + mListView = (ListView) findViewById(android.R.id.list); + mEmptyView = (TextView) findViewById(android.R.id.empty); + + mBookmark = new BookmarkEnhance(this); + mCursor = mBookmark.query(); + mAdapter = new BookmarkAdapter(this, R.layout.bookmark_item, null, new String[] {}, + new int[] {}); + mListView.setEmptyView(mEmptyView); + mListView.setAdapter(mAdapter); + mAdapter.changeCursor(mCursor); + + mListView.setOnItemClickListener(this); + registerForContextMenu(mListView); + } + + @Override + protected void onStart() { + super.onStart(); + } + + @Override + protected void onResume() { + super.onResume(); + } + + @Override + protected void onPause() { + super.onPause(); + } + + @Override + protected void onDestroy() { + if (mAdapter != null) { + mAdapter.changeCursor(null); + } + super.onDestroy(); + } + + @Override + public boolean onCreateOptionsMenu(final Menu menu) { + super.onCreateOptionsMenu(menu); + menu.add(0, MENU_DELETE_ALL, 0, R.string.delete_all) + .setIcon(android.R.drawable.ic_menu_delete); + return true; + } + + @Override + public boolean onOptionsItemSelected(final MenuItem item) { + switch (item.getItemId()) { + case MENU_DELETE_ALL: + mBookmark.deleteAll(); + return true; + default: + break; + } + return super.onOptionsItemSelected(item); + } + + private class BookmarkAdapter extends SimpleCursorAdapter { + + public BookmarkAdapter(final Context context, final int layout, final Cursor c, + final String[] from, final int[] to) { + super(context, layout, c, from, to); + } + + @Override + public View newView(final Context context, final Cursor cursor, final ViewGroup parent) { + final View view = super.newView(context, cursor, parent); + final ViewHolder holder = new ViewHolder(); + holder.mTitleView = (TextView) view.findViewById(R.id.title); + holder.mDataView = (TextView) view.findViewById(R.id.data); + view.setTag(holder); + return view; + } + + @Override + public void bindView(final View view, final Context context, final Cursor cursor) { + final ViewHolder holder = (ViewHolder) view.getTag(); + holder.mId = cursor.getLong(BookmarkEnhance.INDEX_ID); + holder.mTitle = cursor.getString(BookmarkEnhance.INDEX_TITLE); + holder.mData = cursor.getString(BookmarkEnhance.INDEX_DATA); + holder.mMimetype = cursor.getString(BookmarkEnhance.INDEX_MIME_TYPE); + holder.mTitleView.setText(holder.mTitle); + holder.mDataView.setText(holder.mData); + } + + @Override + public void changeCursor(final Cursor c) { + super.changeCursor(c); + } + + } + + private class ViewHolder { + long mId; + String mTitle; + String mData; + String mMimetype; + TextView mTitleView; + TextView mDataView; + } + + @Override + public void onItemClick(final AdapterView<?> parent, final View view, final int position, + final long id) { + final Object o = view.getTag(); + if (o instanceof ViewHolder) { + final ViewHolder holder = (ViewHolder) o; + finish(); + final Intent intent = new Intent(this, MovieActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + String mime = "video/*"; + if (!(holder.mMimetype == null || "".equals(holder.mMimetype.trim()))) { + mime = holder.mMimetype; + } + intent.setDataAndType(Uri.parse(holder.mData), mime); + startActivity(intent); + } + if (LOG) { + Log.v(TAG, "onItemClick(" + position + ", " + id + ")"); + } + } + + @Override + public void onCreateContextMenu(final ContextMenu menu, final View v, + final ContextMenuInfo menuInfo) { + super.onCreateContextMenu(menu, v, menuInfo); + menu.add(0, MENU_DELETE_ONE, 0, R.string.delete); + menu.add(0, MENU_EDIT, 0, R.string.edit); + } + + @Override + public boolean onContextItemSelected(final MenuItem item) { + final AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo(); + switch (item.getItemId()) { + case MENU_DELETE_ONE: + mBookmark.delete(info.id); + return true; + case MENU_EDIT: + final Object obj = info.targetView.getTag(); + if (obj instanceof ViewHolder) { + showEditDialog((ViewHolder) obj); + } else { + Log.w(TAG, "wrong context item info " + info); + } + return true; + default: + return super.onContextItemSelected(item); + } + } + + private void showEditDialog(final ViewHolder holder) { + if (LOG) { + Log.v(TAG, "showEditDialog(" + holder + ")"); + } + if (holder == null) { + return; + } + final LayoutInflater inflater = LayoutInflater.from(this); + final View v = inflater.inflate(R.layout.bookmark_edit_dialog, null); + final EditText titleView = (EditText) v.findViewById(R.id.title); + final EditText dataView = (EditText) v.findViewById(R.id.data); + titleView.setText(holder.mTitle); + dataView.setText(holder.mData); + + final AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.edit); + builder.setView(v); + builder.setIcon(R.drawable.ic_menu_display_bookmark); + builder.setPositiveButton(android.R.string.ok, new OnClickListener() { + + @Override + public void onClick(final DialogInterface dialog, final int which) { + mBookmark.update(holder.mId, titleView.getText().toString(), + dataView.getText().toString(), 0); + } + + }); + builder.setNegativeButton(android.R.string.cancel, null); + final AlertDialog dialog = builder.create(); + dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); + dialog.setInverseBackgroundForced(true); + dialog.show(); + } +} diff --git a/src/org/codeaurora/gallery3d/video/BookmarkEnhance.java b/src/org/codeaurora/gallery3d/video/BookmarkEnhance.java new file mode 100644 index 000000000..fc3ed9b0f --- /dev/null +++ b/src/org/codeaurora/gallery3d/video/BookmarkEnhance.java @@ -0,0 +1,138 @@ +package org.codeaurora.gallery3d.video; + +import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.util.Log; + +import com.android.gallery3d.R; + +public class BookmarkEnhance { + private static final String TAG = "BookmarkEnhance"; + private static final boolean LOG = true; + + private static final Uri BOOKMARK_URI = Uri.parse("content://media/internal/bookmark"); + + public static final String COLUMN_ID = "_id"; + public static final String COLUMN_DATA = "_data"; + public static final String COLUMN_TITLE = "_display_name"; + public static final String COLUMN_ADD_DATE = "date_added"; + public static final String COLUMN_MEDIA_TYPE = "mime_type"; + private static final String COLUMN_POSITION = "position"; + private static final String COLUMN_MIME_TYPE = "media_type"; + + private static final String NULL_HOCK = COLUMN_POSITION; + public static final String ORDER_COLUMN = COLUMN_ADD_DATE + " ASC "; + private static final String VIDEO_STREAMING_MEDIA_TYPE = "streaming"; + + public static final int INDEX_ID = 0; + public static final int INDEX_DATA = 1; + public static final int INDEX_TITLE = 2; + public static final int INDEX_ADD_DATE = 3; + public static final int INDEX_MIME_TYPE = 4; + private static final int INDEX_POSITION = 5; + private static final int INDEX_MEDIA_TYPE = 6; + + public static final String[] PROJECTION = new String[] { + COLUMN_ID, + COLUMN_DATA, + COLUMN_TITLE, + COLUMN_ADD_DATE, + COLUMN_MIME_TYPE, + }; + + private final Context mContext; + private final ContentResolver mCr; + + public BookmarkEnhance(final Context context) { + mContext = context; + mCr = context.getContentResolver(); + } + + public Uri insert(final String title, final String uri, final String mimeType, + final long position) { + final ContentValues values = new ContentValues(); + final String mytitle = (title == null ? mContext.getString(R.string.default_title) : title); + values.put(COLUMN_TITLE, mytitle); + values.put(COLUMN_DATA, uri); + values.put(COLUMN_POSITION, position); + values.put(COLUMN_ADD_DATE, System.currentTimeMillis()); + values.put(COLUMN_MEDIA_TYPE, VIDEO_STREAMING_MEDIA_TYPE); + values.put(COLUMN_MIME_TYPE, mimeType); + final Uri insertUri = mCr.insert(BOOKMARK_URI, values); + if (LOG) { + Log.v(TAG, "insert(" + title + "," + uri + ", " + position + ") return " + + insertUri); + } + return insertUri; + } + + public int delete(final long id) { + final Uri uri = ContentUris.withAppendedId(BOOKMARK_URI, id); + final int count = mCr.delete(uri, null, null); + if (LOG) { + Log.v(TAG, "delete(" + id + ") return " + count); + } + return count; + } + + public int deleteAll() { + final int count = mCr.delete(BOOKMARK_URI, COLUMN_MEDIA_TYPE + "=? ", new String[] { + VIDEO_STREAMING_MEDIA_TYPE + }); + if (LOG) { + Log.v(TAG, "deleteAll() return " + count); + } + return count; + } + + public boolean exists(final String uri) { + final Cursor cursor = mCr.query(BOOKMARK_URI, + PROJECTION, + COLUMN_DATA + "=? and " + COLUMN_MEDIA_TYPE + "=? ", + new String[] { + uri, VIDEO_STREAMING_MEDIA_TYPE + }, + null + ); + boolean exist = false; + if (cursor != null) { + exist = cursor.moveToFirst(); + cursor.close(); + } + if (LOG) { + Log.v(TAG, "exists(" + uri + ") return " + exist); + } + return exist; + } + + public Cursor query() { + final Cursor cursor = mCr.query(BOOKMARK_URI, + PROJECTION, + COLUMN_MEDIA_TYPE + "='" + VIDEO_STREAMING_MEDIA_TYPE + "' ", + null, + ORDER_COLUMN + ); + if (LOG) { + Log.v(TAG, "query() return cursor=" + (cursor == null ? -1 : cursor.getCount())); + } + return cursor; + } + + public int update(final long id, final String title, final String uri, final int position) { + final ContentValues values = new ContentValues(); + values.put(COLUMN_TITLE, title); + values.put(COLUMN_DATA, uri); + values.put(COLUMN_POSITION, position); + final Uri updateUri = ContentUris.withAppendedId(BOOKMARK_URI, id); + final int count = mCr.update(updateUri, values, null, null); + if (LOG) { + Log.v(TAG, "update(" + id + ", " + title + ", " + uri + ", " + position + ")" + + " return " + count); + } + return count; + } +} diff --git a/src/org/codeaurora/gallery3d/video/BookmarkHooker.java b/src/org/codeaurora/gallery3d/video/BookmarkHooker.java new file mode 100644 index 000000000..9d7697d7a --- /dev/null +++ b/src/org/codeaurora/gallery3d/video/BookmarkHooker.java @@ -0,0 +1,76 @@ +package org.codeaurora.gallery3d.video; + +import android.content.Intent; +import android.view.Menu; +import android.view.MenuItem; + +import com.android.gallery3d.R; +import org.codeaurora.gallery3d.ext.MovieUtils; + +public class BookmarkHooker extends MovieHooker { + private static final String TAG = "BookmarkHooker"; + private static final boolean LOG = true; + + private static final String ACTION_BOOKMARK = "org.codeaurora.bookmark.VIEW"; + private static final int MENU_BOOKMARK_ADD = 1; + private static final int MENU_BOOKMARK_DISPLAY = 2; + private MenuItem mMenuBookmarks; + private MenuItem mMenuBookmarkAdd; + + public static final String KEY_LOGO_BITMAP = "logo-bitmap"; + + @Override + public boolean onCreateOptionsMenu(final Menu menu) { + super.onCreateOptionsMenu(menu); + mMenuBookmarkAdd = menu.add(0, getMenuActivityId(MENU_BOOKMARK_ADD), 0, + R.string.bookmark_add); + mMenuBookmarks = menu.add(0, getMenuActivityId(MENU_BOOKMARK_DISPLAY), 0, + R.string.bookmark_display); + return true; + } + + @Override + public boolean onPrepareOptionsMenu(final Menu menu) { + super.onPrepareOptionsMenu(menu); + if (MovieUtils.isLocalFile(getMovieItem().getUri(), getMovieItem().getMimeType())) { + if (mMenuBookmarkAdd != null) { + mMenuBookmarkAdd.setVisible(false); + } + if (mMenuBookmarks != null) { + mMenuBookmarks.setVisible(false); + } + } else { + if (mMenuBookmarkAdd != null) { + mMenuBookmarkAdd.setVisible(true); + } + if (mMenuBookmarks != null) { + mMenuBookmarks.setVisible(true); + } + } + return true; + } + + @Override + public boolean onOptionsItemSelected(final MenuItem item) { + super.onOptionsItemSelected(item); + switch (getMenuOriginalId(item.getItemId())) { + case MENU_BOOKMARK_ADD: + getPlayer().addBookmark(); + return true; + case MENU_BOOKMARK_DISPLAY: + gotoBookmark(); + return true; + default: + return false; + } + } + + private void gotoBookmark() { + final Intent intent = new Intent(ACTION_BOOKMARK); + intent.addCategory(Intent.CATEGORY_DEFAULT); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP + | Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY); + intent.putExtra(KEY_LOGO_BITMAP, getIntent().getParcelableExtra(KEY_LOGO_BITMAP)); + getContext().startActivity(intent); + } +} diff --git a/src/org/codeaurora/gallery3d/video/CodeauroraVideoView.java b/src/org/codeaurora/gallery3d/video/CodeauroraVideoView.java new file mode 100755 index 000000000..39bf532e6 --- /dev/null +++ b/src/org/codeaurora/gallery3d/video/CodeauroraVideoView.java @@ -0,0 +1,1032 @@ +package org.codeaurora.gallery3d.video; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.res.Resources; +import android.media.AudioManager; +import android.media.MediaPlayer; +import android.media.Metadata; +import android.media.MediaPlayer.OnCompletionListener; +import android.media.MediaPlayer.OnErrorListener; +import android.media.MediaPlayer.OnInfoListener; +import android.net.Uri; +import android.os.Handler; +import android.os.Message; +import android.util.AttributeSet; +import android.util.Log; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.View; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; +import android.widget.MediaController; +import android.widget.MediaController.MediaPlayerControl; + +import java.io.IOException; +import java.util.Map; + +/** + * Displays a video file. The VideoView class + * can load images from various sources (such as resources or content + * providers), takes care of computing its measurement from the video so that + * it can be used in any layout manager, and provides various display options + * such as scaling and tinting. + */ +public class CodeauroraVideoView extends SurfaceView implements MediaPlayerControl { + private static final boolean LOG = true; + private String TAG = "CodeauroraVideoView"; + // settable by the client + private Uri mUri; + private Map<String, String> mHeaders; + + // all possible internal states + private static final int STATE_ERROR = -1; + private static final int STATE_IDLE = 0; + private static final int STATE_PREPARING = 1; + private static final int STATE_PREPARED = 2; + private static final int STATE_PLAYING = 3; + private static final int STATE_PAUSED = 4; + private static final int STATE_PLAYBACK_COMPLETED = 5; + private static final int STATE_SUSPENDED = 6; + private static final int MSG_LAYOUT_READY = 1; + + // mCurrentState is a VideoView object's current state. + // mTargetState is the state that a method caller intends to reach. + // For instance, regardless the VideoView object's current state, + // calling pause() intends to bring the object to a target state + // of STATE_PAUSED. + private int mCurrentState = STATE_IDLE; + private int mTargetState = STATE_IDLE; + + // All the stuff we need for playing and showing a video + private SurfaceHolder mSurfaceHolder = null; + private MediaPlayer mMediaPlayer = null; + private int mAudioSession; + private int mVideoWidth; + private int mVideoHeight; + private int mSurfaceWidth; + private int mSurfaceHeight; + private int mDuration; + private MediaController mMediaController; + private OnCompletionListener mOnCompletionListener; + private MediaPlayer.OnPreparedListener mOnPreparedListener; + private MediaPlayer.OnBufferingUpdateListener mOnBufferingUpdateListener; + private MediaPlayer.OnVideoSizeChangedListener mVideoSizeListener; + private MediaPlayer.OnPreparedListener mPreparedListener; + private int mCurrentBufferPercentage; + private OnErrorListener mOnErrorListener; + private OnInfoListener mOnInfoListener; + private int mSeekWhenPrepared; // recording the seek position while preparing + private boolean mCanPause; + private boolean mCanSeekBack; + private boolean mCanSeekForward; + private boolean mHasGotPreparedCallBack = false; + private boolean mNeedWaitLayout = false; + private boolean mHasGotMetaData = false; + private boolean mOnResumed; + + private final Handler mHandler = new Handler() { + public void handleMessage(final Message msg) { + if (LOG) { + Log.v(TAG, "handleMessage() to do prepare. msg=" + msg); + } + switch (msg.what) { + case MSG_LAYOUT_READY: + if (mMediaPlayer == null || mUri == null) { + Log.w(TAG, "Cannot prepare play! mMediaPlayer=" + mMediaPlayer + + ", mUri=" + mUri); + } else { + doPreparedIfReady(mMediaPlayer); + } + break; + default: + Log.w(TAG, "Unhandled message " + msg); + break; + } + } + }; + + public CodeauroraVideoView(Context context) { + super(context); + initVideoView(); + initialize(); + } + + public CodeauroraVideoView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + initVideoView(); + initialize(); + } + + public CodeauroraVideoView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + initVideoView(); + initialize(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width = getDefaultSize(mVideoWidth, widthMeasureSpec); + int height = getDefaultSize(mVideoHeight, heightMeasureSpec); + if (mVideoWidth > 0 && mVideoHeight > 0) { + + int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); + int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); + int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); + int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); + + if (widthSpecMode == MeasureSpec.EXACTLY && heightSpecMode == MeasureSpec.EXACTLY) { + // the size is fixed + width = widthSpecSize; + height = heightSpecSize; + + // for compatibility, we adjust size based on aspect ratio + if ( mVideoWidth * height < width * mVideoHeight ) { + width = height * mVideoWidth / mVideoHeight; + } else if ( mVideoWidth * height > width * mVideoHeight ) { + height = width * mVideoHeight / mVideoWidth; + } + } else if (widthSpecMode == MeasureSpec.EXACTLY) { + // only the width is fixed, adjust the height to match aspect ratio if possible + width = widthSpecSize; + height = width * mVideoHeight / mVideoWidth; + if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) { + // couldn't match aspect ratio within the constraints + height = heightSpecSize; + } + } else if (heightSpecMode == MeasureSpec.EXACTLY) { + // only the height is fixed, adjust the width to match aspect ratio if possible + height = heightSpecSize; + width = height * mVideoWidth / mVideoHeight; + if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) { + // couldn't match aspect ratio within the constraints + width = widthSpecSize; + } + } else { + // neither the width nor the height are fixed, try to use actual video size + width = mVideoWidth; + height = mVideoHeight; + if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) { + height = heightSpecSize; + width = height * mVideoWidth / mVideoHeight; + } + if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) { + width = widthSpecSize; + height = width * mVideoHeight / mVideoWidth; + } + } + } else { + // no size yet, just adopt the given spec sizes + } + setMeasuredDimension(width, height); + if (mNeedWaitLayout) { // when OnMeasure ok, start video. + mNeedWaitLayout = false; + mHandler.sendEmptyMessage(MSG_LAYOUT_READY); + } + } + + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + event.setClassName(CodeauroraVideoView.class.getName()); + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(CodeauroraVideoView.class.getName()); + } + + public int resolveAdjustedSize(int desiredSize, int measureSpec) { + return getDefaultSize(desiredSize, measureSpec); + } + + private void initVideoView() { + mVideoWidth = 0; + mVideoHeight = 0; + getHolder().addCallback(mSHCallback); + getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); + setFocusable(true); + setFocusableInTouchMode(true); + requestFocus(); + mCurrentState = STATE_IDLE; + mTargetState = STATE_IDLE; + } + + private void initialize() { + mPreparedListener = new MediaPlayer.OnPreparedListener() { + public void onPrepared(final MediaPlayer mp) { + if (LOG) { + Log.v(TAG, "mPreparedListener.onPrepared(" + mp + ")"); + } + //Here we can get meta data from mediaplayer. + // Get the capabilities of the player for this stream + final Metadata data = mp.getMetadata(MediaPlayer.METADATA_ALL, + MediaPlayer.BYPASS_METADATA_FILTER); + if (data != null) { + mCanPause = !data.has(Metadata.PAUSE_AVAILABLE) + || data.getBoolean(Metadata.PAUSE_AVAILABLE); + mCanSeekBack = !data.has(Metadata.SEEK_BACKWARD_AVAILABLE) + || data.getBoolean(Metadata.SEEK_BACKWARD_AVAILABLE); + mCanSeekForward = !data.has(Metadata.SEEK_FORWARD_AVAILABLE) + || data.getBoolean(Metadata.SEEK_FORWARD_AVAILABLE); + } else { + mCanPause = true; + mCanSeekBack = true; + mCanSeekForward = true; + Log.w(TAG, "Metadata is null!"); + } + if (LOG) { + Log.v(TAG, "mPreparedListener.onPrepared() mCanPause=" + mCanPause); + } + mHasGotPreparedCallBack = true; + doPreparedIfReady(mMediaPlayer); + } + }; + + mErrorListener = new MediaPlayer.OnErrorListener() { + public boolean onError(final MediaPlayer mp, final int frameworkErr, final int implErr) { + Log.d(TAG, "Error: " + frameworkErr + "," + implErr); + if (mCurrentState == STATE_ERROR) { + Log.w(TAG, "Duplicate error message. error message has been sent! " + + "error=(" + frameworkErr + "," + implErr + ")"); + return true; + } + //record error position and duration + //here disturb the original logic + mSeekWhenPrepared = getCurrentPosition(); + if (LOG) { + Log.v(TAG, "onError() mSeekWhenPrepared=" + mSeekWhenPrepared + ", mDuration=" + mDuration); + } + //for old version Streaming server, getduration is not valid. + mDuration = Math.abs(mDuration); + mCurrentState = STATE_ERROR; + mTargetState = STATE_ERROR; + if (mMediaController != null) { + mMediaController.hide(); + } + + /* If an error handler has been supplied, use it and finish. */ + if (mOnErrorListener != null) { + if (mOnErrorListener.onError(mMediaPlayer, frameworkErr, implErr)) { + return true; + } + } + + /* Otherwise, pop up an error dialog so the user knows that + * something bad has happened. Only try and pop up the dialog + * if we're attached to a window. When we're going away and no + * longer have a window, don't bother showing the user an error. + */ + if (getWindowToken() != null) { + final Resources r = mContext.getResources(); + int messageId; + + if (frameworkErr == MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK) { + messageId = com.android.internal.R.string.VideoView_error_text_invalid_progressive_playback; + } else { + messageId = com.android.internal.R.string.VideoView_error_text_unknown; + } + new AlertDialog.Builder(mContext) + .setMessage(messageId) + .setPositiveButton(com.android.internal.R.string.VideoView_error_button, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + /* If we get here, there is no onError listener, so + * at least inform them that the video is over. + */ + if (mOnCompletionListener != null) { + mOnCompletionListener.onCompletion(mMediaPlayer); + } + } + }) + .setCancelable(false) + .show(); + } + return true; + } + }; + + mBufferingUpdateListener = new MediaPlayer.OnBufferingUpdateListener() { + public void onBufferingUpdate(final MediaPlayer mp, final int percent) { + mCurrentBufferPercentage = percent; + if (mOnBufferingUpdateListener != null) { + mOnBufferingUpdateListener.onBufferingUpdate(mp, percent); + } + if (LOG) { + Log.v(TAG, "onBufferingUpdate() Buffering percent: " + percent); + Log.v(TAG, "onBufferingUpdate() mTargetState=" + mTargetState); + Log.v(TAG, "onBufferingUpdate() mCurrentState=" + mCurrentState); + } + } + }; + + mSizeChangedListener = new MediaPlayer.OnVideoSizeChangedListener() { + public void onVideoSizeChanged(final MediaPlayer mp, final int width, final int height) { + mVideoWidth = mp.getVideoWidth(); + mVideoHeight = mp.getVideoHeight(); + if (LOG) { + Log.v(TAG, "OnVideoSizeChagned(" + width + "," + height + ")"); + Log.v(TAG, "OnVideoSizeChagned(" + mVideoWidth + "," + mVideoHeight + ")"); + Log.v(TAG, "OnVideoSizeChagned() mCurrentState=" + mCurrentState); + } + if (mVideoWidth != 0 && mVideoHeight != 0) { + getHolder().setFixedSize(mVideoWidth, mVideoHeight); + if (mCurrentState == STATE_PREPARING) { + mNeedWaitLayout = true; + } + } + if (mVideoSizeListener != null) { + mVideoSizeListener.onVideoSizeChanged(mp, width, height); + } + CodeauroraVideoView.this.requestLayout(); + } + }; + + getHolder().removeCallback(mSHCallback); + mSHCallback = new SurfaceHolder.Callback() { + public void surfaceChanged(final SurfaceHolder holder, final int format, final int w, final int h) { + if (LOG) { + Log.v(TAG, "surfaceChanged(" + holder + ", " + format + ", " + w + ", " + h + ")"); + Log.v(TAG, "surfaceChanged() mMediaPlayer=" + mMediaPlayer + ", mTargetState=" + mTargetState + + ", mVideoWidth=" + mVideoWidth + ", mVideoHeight=" + mVideoHeight); + } + mSurfaceWidth = w; + mSurfaceHeight = h; + final boolean isValidState = (mTargetState == STATE_PLAYING); + final boolean hasValidSize = (mVideoWidth == w && mVideoHeight == h); + if (mMediaPlayer != null && isValidState && hasValidSize) { + if (mSeekWhenPrepared != 0) { + seekTo(mSeekWhenPrepared); + } + Log.v(TAG, "surfaceChanged() start()"); + start(); + } + } + + public void surfaceCreated(final SurfaceHolder holder) { + if (LOG) { + Log.v(TAG, "surfaceCreated(" + holder + ")"); + } + mSurfaceHolder = holder; + openVideo(); + } + + public void surfaceDestroyed(final SurfaceHolder holder) { + // after we return from this we can't use the surface any more + if (LOG) { + Log.v(TAG, "surfaceDestroyed(" + holder + ")"); + } + mSurfaceHolder = null; + if (mMediaController != null) { + mMediaController.hide(); + } + release(true); + } + }; + getHolder().addCallback(mSHCallback); + } + + public void setVideoPath(String path) { + setVideoURI(Uri.parse(path)); + } + + public void setVideoURI(Uri uri) { + setVideoURI(uri, null); + } + + /** + * @hide + */ + public void setVideoURI(Uri uri, Map<String, String> headers) { + Log.d(TAG,"setVideoURI uri = " + uri); + mDuration = -1; + setResumed(true); + mUri = uri; + mHeaders = headers; + mSeekWhenPrepared = 0; + openVideo(); + requestLayout(); + invalidate(); + } + + public void stopPlayback() { + if (mMediaPlayer != null) { + mMediaPlayer.stop(); + mMediaPlayer.release(); + mMediaPlayer = null; + mCurrentState = STATE_IDLE; + mTargetState = STATE_IDLE; + } + } + + private void openVideo() { + clearVideoInfo(); + if (mUri == null || mSurfaceHolder == null) { + // not ready for playback just yet, will try again later + return; + } + // Tell the music playback service to pause + // TODO: these constants need to be published somewhere in the framework. + Intent i = new Intent("com.android.music.musicservicecommand"); + i.putExtra("command", "pause"); + mContext.sendBroadcast(i); + // we shouldn't clear the target state, because somebody might have + // called start() previously + release(false); + if ("".equalsIgnoreCase(String.valueOf(mUri))) { + Log.w(TAG, "Unable to open content: " + mUri); + mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0); + return; + } + try { + mMediaPlayer = new MediaPlayer(); + if (mAudioSession != 0) { + mMediaPlayer.setAudioSessionId(mAudioSession); + } else { + mAudioSession = mMediaPlayer.getAudioSessionId(); + } + mMediaPlayer.setOnPreparedListener(mPreparedListener); + mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener); + mMediaPlayer.setOnCompletionListener(mCompletionListener); + mMediaPlayer.setOnErrorListener(mErrorListener); + mMediaPlayer.setOnInfoListener(mOnInfoListener); + mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener); + mCurrentBufferPercentage = 0; + mMediaPlayer.setDataSource(mContext, mUri, mHeaders); + mMediaPlayer.setDisplay(mSurfaceHolder); + mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); + mMediaPlayer.setScreenOnWhilePlaying(true); + mMediaPlayer.prepareAsync(); + // we don't set the target state here either, but preserve the + // target state that was there before. + mCurrentState = STATE_PREPARING; + attachMediaController(); + } catch (IOException ex) { + Log.w(TAG, "Unable to open content: " + mUri, ex); + mCurrentState = STATE_ERROR; + mTargetState = STATE_ERROR; + mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0); + return; + } catch (IllegalArgumentException ex) { + Log.w(TAG, "Unable to open content: " + mUri, ex); + mCurrentState = STATE_ERROR; + mTargetState = STATE_ERROR; + mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0); + return; + } + } + + public void setMediaController(MediaController controller) { + if (mMediaController != null) { + mMediaController.hide(); + } + mMediaController = controller; + attachMediaController(); + } + + private void attachMediaController() { + if (mMediaPlayer != null && mMediaController != null) { + mMediaController.setMediaPlayer(this); + View anchorView = this.getParent() instanceof View ? + (View)this.getParent() : this; + mMediaController.setAnchorView(anchorView); + mMediaController.setEnabled(isInPlaybackState()); + } + } + + private boolean isHTTPStreaming(Uri mUri) { + if (mUri != null){ + String scheme = mUri.toString(); + if (scheme.startsWith("http://") || scheme.startsWith("https://")) { + if (scheme.endsWith(".m3u8") || scheme.endsWith(".m3u") + || scheme.contains("m3u8") || scheme.endsWith(".mpd")) { + // HLS or DASH streaming source + return false; + } + // HTTP streaming + return true; + } + } + return false; + } + + MediaPlayer.OnVideoSizeChangedListener mSizeChangedListener = + new MediaPlayer.OnVideoSizeChangedListener() { + public void onVideoSizeChanged(MediaPlayer mp, int width, int height) { + mVideoWidth = mp.getVideoWidth(); + mVideoHeight = mp.getVideoHeight(); + if (mVideoWidth != 0 && mVideoHeight != 0) { + getHolder().setFixedSize(mVideoWidth, mVideoHeight); + requestLayout(); + } + } + }; + + private MediaPlayer.OnCompletionListener mCompletionListener = + new MediaPlayer.OnCompletionListener() { + public void onCompletion(MediaPlayer mp) { + mCurrentState = STATE_PLAYBACK_COMPLETED; + mTargetState = STATE_PLAYBACK_COMPLETED; + if (mMediaController != null) { + mMediaController.hide(); + } + if (mOnCompletionListener != null) { + mOnCompletionListener.onCompletion(mMediaPlayer); + } + } + }; + + private MediaPlayer.OnErrorListener mErrorListener = + new MediaPlayer.OnErrorListener() { + public boolean onError(MediaPlayer mp, int framework_err, int impl_err) { + Log.d(TAG, "Error: " + framework_err + "," + impl_err); + mCurrentState = STATE_ERROR; + mTargetState = STATE_ERROR; + if (mMediaController != null) { + mMediaController.hide(); + } + + /* If an error handler has been supplied, use it and finish. */ + if (mOnErrorListener != null) { + if (mOnErrorListener.onError(mMediaPlayer, framework_err, impl_err)) { + return true; + } + } + + /* Otherwise, pop up an error dialog so the user knows that + * something bad has happened. Only try and pop up the dialog + * if we're attached to a window. When we're going away and no + * longer have a window, don't bother showing the user an error. + */ + if (getWindowToken() != null) { + Resources r = mContext.getResources(); + int messageId; + + if (framework_err == MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK) { + messageId = com.android.internal.R.string.VideoView_error_text_invalid_progressive_playback; + } else { + messageId = com.android.internal.R.string.VideoView_error_text_unknown; + } + + new AlertDialog.Builder(mContext) + .setMessage(messageId) + .setPositiveButton(com.android.internal.R.string.VideoView_error_button, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + /* If we get here, there is no onError listener, so + * at least inform them that the video is over. + */ + if (mOnCompletionListener != null) { + mOnCompletionListener.onCompletion(mMediaPlayer); + } + } + }) + .setCancelable(false) + .show(); + } + return true; + } + }; + + private MediaPlayer.OnBufferingUpdateListener mBufferingUpdateListener = + new MediaPlayer.OnBufferingUpdateListener() { + public void onBufferingUpdate(MediaPlayer mp, int percent) { + mCurrentBufferPercentage = percent; + } + }; + + /** + * Register a callback to be invoked when the media file + * is loaded and ready to go. + * + * @param l The callback that will be run + */ + public void setOnPreparedListener(MediaPlayer.OnPreparedListener l) { + mOnPreparedListener = l; + } + + /** + * Register a callback to be invoked when the end of a media file + * has been reached during playback. + * + * @param l The callback that will be run + */ + public void setOnCompletionListener(OnCompletionListener l) { + mOnCompletionListener = l; + } + + /** + * Register a callback to be invoked when an error occurs + * during playback or setup. If no listener is specified, + * or if the listener returned false, VideoView will inform + * the user of any errors. + * + * @param l The callback that will be run + */ + public void setOnErrorListener(OnErrorListener l) { + mOnErrorListener = l; + } + + /** + * Register a callback to be invoked when an informational event + * occurs during playback or setup. + * + * @param l The callback that will be run + */ + public void setOnInfoListener(OnInfoListener l) { + mOnInfoListener = l; + } + + SurfaceHolder.Callback mSHCallback = new SurfaceHolder.Callback() { + public void surfaceChanged(SurfaceHolder holder, int format, + int w, int h) { + mSurfaceWidth = w; + mSurfaceHeight = h; + boolean isValidState = (mTargetState == STATE_PLAYING); + boolean hasValidSize = (mVideoWidth == w && mVideoHeight == h); + if (mMediaPlayer != null && isValidState && hasValidSize) { + if (mSeekWhenPrepared != 0) { + seekTo(mSeekWhenPrepared); + } + start(); + } + } + + public void surfaceCreated(SurfaceHolder holder) { + /* + if (mCurrentState == STATE_SUSPENDED) { + mSurfaceHolder = holder; + mMediaPlayer.setDisplay(mSurfaceHolder); + if (mMediaPlayer.resume()) { + mCurrentState = STATE_PREPARED; + if (mSeekWhenPrepared != 0) { + seekTo(mSeekWhenPrepared); + } + if (mTargetState == STATE_PLAYING) { + start(); + } + return; + } else { + release(false); + } + } + mSurfaceHolder = holder; + openVideo(); + */ + + if (LOG) { + Log.v(TAG, "surfaceCreated(" + holder + ")"); + } + if (mCurrentState == STATE_SUSPENDED) { + mSurfaceHolder = holder; + mMediaPlayer.setDisplay(mSurfaceHolder); + release(false); + } + mSurfaceHolder = holder; + openVideo(); + } + + public void surfaceDestroyed(SurfaceHolder holder) { + // after we return from this we can't use the surface any more + mSurfaceHolder = null; + if (mMediaController != null) mMediaController.hide(); + if (isHTTPStreaming(mUri) && mCurrentState == STATE_SUSPENDED) { + // Don't call release() while run suspend operation + return; + } + release(true); + } + }; + + /* + * release the media player in any state + */ + private void release(boolean cleartargetstate) { + if (mMediaPlayer != null) { + mMediaPlayer.reset(); + mMediaPlayer.release(); + mMediaPlayer = null; + mCurrentState = STATE_IDLE; + if (cleartargetstate) { + mTargetState = STATE_IDLE; + } + } + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + if (isInPlaybackState() && mMediaController != null) { + toggleMediaControlsVisiblity(); + } + return false; + } + + @Override + public boolean onTrackballEvent(MotionEvent ev) { + if (isInPlaybackState() && mMediaController != null) { + toggleMediaControlsVisiblity(); + } + return false; + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + final boolean isKeyCodeSupported = keyCode != KeyEvent.KEYCODE_BACK && + keyCode != KeyEvent.KEYCODE_VOLUME_UP && + keyCode != KeyEvent.KEYCODE_VOLUME_DOWN && + keyCode != KeyEvent.KEYCODE_VOLUME_MUTE && + keyCode != KeyEvent.KEYCODE_MENU && + keyCode != KeyEvent.KEYCODE_CALL && + keyCode != KeyEvent.KEYCODE_ENDCALL && + keyCode != KeyEvent.KEYCODE_CAMERA; + if (isInPlaybackState() && isKeyCodeSupported && mMediaController != null) { + if (event.getRepeatCount() == 0 && (keyCode == KeyEvent.KEYCODE_HEADSETHOOK || + keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE)) { + if (mMediaPlayer.isPlaying()) { + pause(); + mMediaController.show(); + } else { + start(); + mMediaController.hide(); + } + return true; + } else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) { + if (!mMediaPlayer.isPlaying()) { + start(); + mMediaController.hide(); + } + return true; + } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP + || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) { + if (mMediaPlayer.isPlaying()) { + pause(); + mMediaController.show(); + } + return true; + } else if (keyCode == KeyEvent.KEYCODE_MEDIA_FAST_FORWARD || + keyCode == KeyEvent.KEYCODE_MEDIA_NEXT || + keyCode == KeyEvent.KEYCODE_MEDIA_PREVIOUS || + keyCode == KeyEvent.KEYCODE_MEDIA_REWIND || + keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE || + keyCode == KeyEvent.KEYCODE_HEADSETHOOK) { + // consume media action, so if video view if front, + // other media player will not play any sounds. + return true; + } else { + toggleMediaControlsVisiblity(); + } + } + return super.onKeyDown(keyCode, event); + } + + private void toggleMediaControlsVisiblity() { + if (mMediaController.isShowing()) { + mMediaController.hide(); + } else { + mMediaController.show(); + } + } + + @Override + public void start() { + if (isInPlaybackState()) { + mMediaPlayer.start(); + mCurrentState = STATE_PLAYING; + } + mTargetState = STATE_PLAYING; + } + + @Override + public void pause() { + if (isInPlaybackState()) { + if (mMediaPlayer.isPlaying()) { + mMediaPlayer.pause(); + mCurrentState = STATE_PAUSED; + } + } + mTargetState = STATE_PAUSED; + } + + public void suspend() { + // HTTP streaming will call mMediaPlayer->suspend(), others will call release() + /* + if (isHTTPStreaming(mUri) && mCurrentState != STATE_PREPARING) { + if (mMediaPlayer.suspend()) { + mTargetState = mCurrentState; + mCurrentState = STATE_SUSPENDED; + return; + } + }*/ + release(false); + } + + public void resume() { + setResumed(true); + openVideo(); + } + + @Override + public int getDuration() { + final boolean inPlaybackState = isInPlaybackState(); + if (LOG) { + Log.v(TAG, "getDuration() mDuration=" + mDuration + ", inPlaybackState=" + + inPlaybackState); + } + if (inPlaybackState) { + if (mDuration > 0) { + return mDuration; + } + // in case the duration is zero or smaller than zero for streaming + // video + int tempDuration = mMediaPlayer.getDuration(); + if (tempDuration <= 0) { + return mDuration; + } else { + mDuration = tempDuration; + } + + return mDuration; + } + return mDuration; + } + + @Override + public int getCurrentPosition() { + int position = 0; + if (mSeekWhenPrepared > 0) { + // if connecting error before seek, + // we should remember this position for retry + position = mSeekWhenPrepared; + // /M: if player not started, getCurrentPosition() will lead to NE. + } else if (isInPlaybackState()) { + position = mMediaPlayer.getCurrentPosition(); + } + if (LOG) { + Log.v(TAG, "getCurrentPosition() return " + position + + ", mSeekWhenPrepared=" + mSeekWhenPrepared); + } + return position; + } + + @Override + public void seekTo(int msec) { + if (isInPlaybackState()) { + mMediaPlayer.seekTo(msec); + mSeekWhenPrepared = 0; + } else { + mSeekWhenPrepared = msec; + } + } + + @Override + public boolean isPlaying() { + return isInPlaybackState() && mMediaPlayer.isPlaying(); + } + + @Override + public int getBufferPercentage() { + if (mMediaPlayer != null) { + return mCurrentBufferPercentage; + } + return 0; + } + + private boolean isInPlaybackState() { + return (mMediaPlayer != null && + mCurrentState != STATE_ERROR && + mCurrentState != STATE_IDLE && + mCurrentState != STATE_PREPARING && + mCurrentState != STATE_SUSPENDED); + } + + @Override + public boolean canPause() { + return mCanPause; + } + + @Override + public boolean canSeekBackward() { + return mCanSeekBack; + } + + @Override + public boolean canSeekForward() { + return mCanSeekForward; + } + + @Override + public int getAudioSessionId() { + if (mAudioSession == 0) { + MediaPlayer foo = new MediaPlayer(); + mAudioSession = foo.getAudioSessionId(); + foo.release(); + } + return mAudioSession; + } + + // for duration displayed + public void setDuration(final int duration) { + if (LOG) { + Log.v(TAG, "setDuration(" + duration + ")"); + } + mDuration = (duration > 0 ? -duration : duration); + } + + public void setVideoURI(final Uri uri, final Map<String, String> headers, + final boolean hasGotMetaData) { + if (LOG) { + Log.v(TAG, "setVideoURI(" + uri + ", " + headers + ")"); + } + // clear the flags + mHasGotMetaData = hasGotMetaData; + setVideoURI(uri, headers); + } + + private void doPreparedIfReady(final MediaPlayer mp) { + if (LOG) { + Log.v(TAG, "doPreparedIfReady() mHasGotPreparedCallBack=" + mHasGotPreparedCallBack + + ", mHasGotMetaData=" + mHasGotMetaData + ", mNeedWaitLayout=" + + mNeedWaitLayout + + ", mCurrentState=" + mCurrentState); + } + if (mHasGotPreparedCallBack && mHasGotMetaData && !mNeedWaitLayout) { + doPrepared(mp); + } + } + + private void doPrepared(final MediaPlayer mp) { + if (LOG) { + Log.v(TAG, "doPrepared(" + mp + ") start"); + } + mCurrentState = STATE_PREPARED; + if (mOnPreparedListener != null) { + mOnPreparedListener.onPrepared(mMediaPlayer); + } + mVideoWidth = mp.getVideoWidth(); + mVideoHeight = mp.getVideoHeight(); + + // mSeekWhenPrepared may be changed after seekTo() + final int seekToPosition = mSeekWhenPrepared; + if (seekToPosition != 0) { + seekTo(seekToPosition); + } + if (mVideoWidth != 0 && mVideoHeight != 0) { + getHolder().setFixedSize(mVideoWidth, mVideoHeight); + } + + if (mTargetState == STATE_PLAYING) { + start(); + } + if (LOG) { + Log.v(TAG, "doPrepared() end video size: " + mVideoWidth + "," + mVideoHeight + + ", mTargetState=" + mTargetState + ", mCurrentState=" + mCurrentState); + } + } + + /** + * surfaceCreate will invoke openVideo after the activity stoped. Here set + * this flag to avoid openVideo after the activity stoped. + * + * @param resume + */ + public void setResumed(final boolean resume) { + if (LOG) { + Log.v(TAG, "setResumed(" + resume + ") mUri=" + mUri + ", mOnResumed=" + mOnResumed); + } + mOnResumed = resume; + } + + private void clearVideoInfo() { + if (LOG) { + Log.v(TAG, "clearVideoInfo()"); + } + mHasGotPreparedCallBack = false; + mNeedWaitLayout = false; + } + + public void clearSeek() { + if (LOG) { + Log.v(TAG, "clearSeek() mSeekWhenPrepared=" + mSeekWhenPrepared); + } + mSeekWhenPrepared = 0; + } + + public void clearDuration() { + if (LOG) { + Log.v(TAG, "clearDuration() mDuration=" + mDuration); + } + mDuration = -1; + } + + public boolean isTargetPlaying() { + if (LOG) { + Log.v(TAG, "isTargetPlaying() mTargetState=" + mTargetState); + } + return mTargetState == STATE_PLAYING; + } +} diff --git a/src/org/codeaurora/gallery3d/video/DmReceiver.java b/src/org/codeaurora/gallery3d/video/DmReceiver.java new file mode 100644 index 000000000..616ce33d6 --- /dev/null +++ b/src/org/codeaurora/gallery3d/video/DmReceiver.java @@ -0,0 +1,65 @@ +package org.codeaurora.gallery3d.video; + +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.Service; +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.res.Resources; +import android.net.Uri; +import android.os.PowerManager; +import android.preference.PreferenceManager; +import android.provider.Settings.System; +import android.util.Log; + +public class DmReceiver extends BroadcastReceiver { + private static final String TAG = "DmReceiver"; + public static final String WRITE_SETTING_ACTION = "streaming.action.WRITE_SETTINGS"; + public static final String BOOT_COMPLETED = "android.intent.action.BOOT_COMPLETED"; + + private SharedPreferences mPref; + static final int STREAMING_CONNPROFILE_IO_HANDLER_TYPE = 1; + static final int STREAMING_MAX_UDP_PORT_IO_HANDLER_TYPE = 3; + static final int STREAMING_MIN_UDP_PORT_IO_HANDLER_TYPE = 4; + + @Override + public void onReceive(Context context, Intent intent) { + if (mPref == null) { + mPref = PreferenceManager.getDefaultSharedPreferences(context); + } + if (BOOT_COMPLETED.equals(intent.getAction())) { + String rtpMaxport = mPref.getString(SettingsActivity.PREFERENCE_RTP_MAXPORT, "65535"); + String rtpMinport = mPref.getString(SettingsActivity.PREFERENCE_RTP_MINPORT, "8192"); + String apn = mPref.getString(SettingsActivity.PREFERENCE_APN, "CMWAP"); + System.putString(context.getContentResolver(), + "streaming_max_udp_port", rtpMaxport); + System.putString(context.getContentResolver(), + "streaming_min_udp_port", rtpMinport); + System.putString(context.getContentResolver(), "apn", apn); + } else if (WRITE_SETTING_ACTION.equals(intent.getAction())) { + int valueType = intent.getIntExtra("type", 0); + String value = intent.getStringExtra("value"); + if (valueType == STREAMING_MAX_UDP_PORT_IO_HANDLER_TYPE) { + mPref.edit().putString(SettingsActivity.PREFERENCE_RTP_MAXPORT, + value).commit(); + System.putString(context.getContentResolver(), + "streaming_max_udp_port", value); + } else if (valueType == STREAMING_MIN_UDP_PORT_IO_HANDLER_TYPE) { + mPref.edit().putString(SettingsActivity.PREFERENCE_RTP_MINPORT, + value).commit(); + System.putString(context.getContentResolver(), + "streaming_min_udp_port", value); + } else if (valueType == STREAMING_CONNPROFILE_IO_HANDLER_TYPE) { + mPref.edit().putString(SettingsActivity.PREFERENCE_APN, + value).commit(); + System.putString(context.getContentResolver(), + "apn", value); + } + } + } +} diff --git a/src/org/codeaurora/gallery3d/video/ExtensionHelper.java b/src/org/codeaurora/gallery3d/video/ExtensionHelper.java index 7694cbfff..f95e9c09f 100755 --- a/src/org/codeaurora/gallery3d/video/ExtensionHelper.java +++ b/src/org/codeaurora/gallery3d/video/ExtensionHelper.java @@ -18,6 +18,7 @@ public class ExtensionHelper { final ActivityHookerGroup group = new ActivityHookerGroup(); boolean loop = context.getResources().getBoolean(R.bool.loop); boolean stereo = context.getResources().getBoolean(R.bool.stereo); + boolean streaming = context.getResources().getBoolean(R.bool.streaming); if (loop == true) { group.addHooker(new LoopVideoHooker()); // add it for common feature. @@ -25,7 +26,10 @@ public class ExtensionHelper { if (stereo == true) { group.addHooker(new StereoAudioHooker()); // add it for common feature. } - + if (streaming == true) { + group.addHooker(new StreamingHooker()); + group.addHooker(new BookmarkHooker()); + } return group; } } diff --git a/src/org/codeaurora/gallery3d/video/MovieTitleHelper.java b/src/org/codeaurora/gallery3d/video/MovieTitleHelper.java new file mode 100644 index 000000000..5f2229d5e --- /dev/null +++ b/src/org/codeaurora/gallery3d/video/MovieTitleHelper.java @@ -0,0 +1,108 @@ +package org.codeaurora.gallery3d.video; + +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteException; +import android.net.Uri; +import android.provider.MediaStore; +import android.provider.OpenableColumns; +import android.util.Log; + + +import java.io.File; + +public class MovieTitleHelper { + private static final String TAG = "MovieTitleHelper"; + private static final boolean LOG = true; + + public static String getTitleFromMediaData(final Context context, final Uri uri) { + String title = null; + Cursor cursor = null; + try { + String data = Uri.decode(uri.toString()); + data = data.replaceAll("'", "''"); + final String where = "_data LIKE '%" + data.replaceFirst("file:///", "") + "'"; + cursor = context.getContentResolver().query( + MediaStore.Video.Media.EXTERNAL_CONTENT_URI, + new String[] { + OpenableColumns.DISPLAY_NAME + }, where, null, null); + if (LOG) { + Log.v( + TAG, + "setInfoFromMediaData() cursor=" + + (cursor == null ? "null" : cursor.getCount())); + } + if (cursor != null && cursor.moveToFirst()) { + title = cursor.getString(0); + } + } catch (final SQLiteException ex) { + ex.printStackTrace(); + } finally { + if (cursor != null) { + cursor.close(); + } + } + if (LOG) { + Log.v(TAG, "setInfoFromMediaData() return " + title); + } + return title; + } + + public static String getTitleFromDisplayName(final Context context, final Uri uri) { + String title = null; + Cursor cursor = null; + try { + cursor = context.getContentResolver().query(uri, + new String[] { + OpenableColumns.DISPLAY_NAME + }, null, null, null); + if (cursor != null && cursor.moveToFirst()) { + title = cursor.getString(0); + } + } catch (final SQLiteException ex) { + ex.printStackTrace(); + } finally { + if (cursor != null) { + cursor.close(); + } + } + if (LOG) { + Log.v(TAG, "getTitleFromDisplayName() return " + title); + } + return title; + } + + public static String getTitleFromUri(final Uri uri) { + final String title = Uri.decode(uri.getLastPathSegment()); + if (LOG) { + Log.v(TAG, "getTitleFromUri() return " + title); + } + return title; + } + + public static String getTitleFromData(final Context context, final Uri uri) { + String title = null; + Cursor cursor = null; + try { + cursor = context.getContentResolver().query(uri, + new String[] { + "_data" + }, null, null, null); + if (cursor != null && cursor.moveToFirst()) { + final File file = new File(cursor.getString(0)); + title = file.getName(); + } + } catch (final SQLiteException ex) { + ex.printStackTrace(); + } finally { + if (cursor != null) { + cursor.close(); + } + } + if (LOG) { + Log.v(TAG, "getTitleFromData() return " + title); + } + return title; + } +} diff --git a/src/org/codeaurora/gallery3d/video/SettingsActivity.java b/src/org/codeaurora/gallery3d/video/SettingsActivity.java new file mode 100755 index 000000000..450f10d7c --- /dev/null +++ b/src/org/codeaurora/gallery3d/video/SettingsActivity.java @@ -0,0 +1,251 @@ +package org.codeaurora.gallery3d.video; + +import android.app.ActionBar; +import android.app.Activity; +import android.content.ContentResolver; +import android.content.Intent; +import android.content.SharedPreferences; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.os.SystemProperties; +import android.preference.CheckBoxPreference; +import android.preference.EditTextPreference; +import android.preference.ListPreference; +import android.preference.Preference; +import android.preference.PreferenceActivity; +import android.preference.PreferenceCategory; +import android.preference.RingtonePreference; +import android.preference.PreferenceScreen; +import android.provider.ContactsContract; +import android.provider.Telephony; +import android.telephony.TelephonyManager; +import android.text.method.DigitsKeyListener; +import android.util.Log; +import android.view.KeyEvent; +import android.view.MenuItem; +import com.android.gallery3d.R; + +import java.util.ArrayList; + +public class SettingsActivity extends PreferenceActivity { + + public static final String PREFERENCE_RTP_MINPORT = "rtp_min_port"; + public static final String PREFERENCE_RTP_MAXPORT = "rtp_max_port"; + public static final String PREFERENCE_RTCP_MINPORT = "rtcp_min_port"; + public static final String PREFERENCE_RTCP_MAXPORT = "rtcp_max_port"; + public static final String PREFERENCE_BUFFER_SIZE = "buffer_size"; + public static final String PREFERENCE_APN = "apn"; + + private EditTextPreference mRtpMinPort; + private EditTextPreference mRtpMaxPort; + private EditTextPreference mRtcpMinPort; + private EditTextPreference mRtcpMaxPort; + private EditTextPreference mBufferSize; + private PreferenceScreen mApn; + private CheckBoxPreference mRepeat; + + private static final int SELECT_APN = 1; + public static final String PREFERRED_APN_URI = "content://telephony/carriers/preferapn"; + private static final Uri PREFERAPN_URI = Uri.parse(PREFERRED_APN_URI); + private static final int ID_INDEX = 0; + private static final int NAME_INDEX = 1; + + private boolean mUseNvOperatorForEhrpd = SystemProperties.getBoolean( + "persist.radio.use_nv_for_ehrpd", false); + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.rtsp_settings_preferences); + + SharedPreferences mPref; + mPref = getPreferenceScreen().getSharedPreferences(); + String rtpMinport = mPref.getString(PREFERENCE_RTP_MINPORT, "8192"); + String rtpMaxport = mPref.getString(PREFERENCE_RTP_MAXPORT, "65535"); + String rtcpMinport = mPref.getString(PREFERENCE_RTCP_MAXPORT, null); + String rtcpMaxport = mPref.getString(PREFERENCE_RTCP_MAXPORT, null); + String bufferSize = mPref.getString(PREFERENCE_BUFFER_SIZE, null); + + mRtpMinPort = (EditTextPreference) findPreference(PREFERENCE_RTP_MINPORT); + mRtpMinPort.getEditText().setKeyListener(DigitsKeyListener.getInstance("0123456789")); + mRtpMinPort.setSummary(rtpMinport); + mRtpMinPort.setText(rtpMinport); + mRtpMinPort.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + public boolean onPreferenceChange(Preference preference, Object newValue) { + final String summary = newValue.toString(); + mRtpMinPort.setSummary(summary); + Log.d("rtsp", "z66 summary = " + summary); + android.provider.Settings.System.putString(getContentResolver(), + "streaming_min_udp_port", summary); + return true; + } + }); + + mRtpMaxPort = (EditTextPreference) findPreference(PREFERENCE_RTP_MAXPORT); + mRtpMaxPort.getEditText().setKeyListener(DigitsKeyListener.getInstance("0123456789")); + mRtpMaxPort.setSummary(rtpMaxport); + mRtpMaxPort.setText(rtpMaxport); + mRtpMaxPort.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + public boolean onPreferenceChange(Preference preference, Object newValue) { + final String summary = newValue.toString(); + mRtpMaxPort.setSummary(summary); + Log.w("rtsp", "z82 summary = " + summary); + android.provider.Settings.System.putString(getContentResolver(), + "streaming_max_udp_port", summary); + return true; + } + }); + mRtcpMinPort = (EditTextPreference) findPreference(PREFERENCE_RTCP_MINPORT); + mRtcpMinPort.getEditText().setKeyListener(DigitsKeyListener.getInstance("0123456789")); + mRtcpMinPort.setSummary(rtcpMinport); + mRtcpMinPort.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + public boolean onPreferenceChange(Preference preference, Object newValue) { + final String summary = newValue.toString(); + mRtcpMinPort.setSummary(summary); + return true; + } + }); + mRtcpMaxPort = (EditTextPreference) findPreference(PREFERENCE_RTCP_MAXPORT); + mRtcpMaxPort.getEditText().setKeyListener(DigitsKeyListener.getInstance("0123456789")); + mRtcpMaxPort.setSummary(rtcpMaxport); + mRtcpMaxPort.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + public boolean onPreferenceChange(Preference preference, Object newValue) { + final String summary = newValue.toString(); + mRtcpMaxPort.setSummary(summary); + return true; + } + }); + + mBufferSize = (EditTextPreference) findPreference(PREFERENCE_BUFFER_SIZE); + mBufferSize.getEditText().setKeyListener(DigitsKeyListener.getInstance("0123456789")); + mBufferSize.setSummary(bufferSize); + mBufferSize.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + public boolean onPreferenceChange(Preference preference, Object newValue) { + final String summary = newValue.toString(); + mBufferSize.setSummary(summary); + return true; + } + }); + + mApn = (PreferenceScreen) findPreference(PREFERENCE_APN); + mApn.setSummary(getDefaultApnName()); + mApn.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + public boolean onPreferenceClick(Preference preference) { + Intent intent = new Intent(); + intent.setClassName("com.android.settings", "com.android.settings.ApnSettings"); + startActivityForResult(intent, SELECT_APN); + return true; + } + }); + + ActionBar ab = getActionBar(); + ab.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD); + ab.setDisplayHomeAsUpEnabled(true); + ab.setTitle(R.string.setting); + } + + private String getDefaultApnName() { + + // to find default key + String key = null; + String name = null; + Cursor cursor = getContentResolver().query(PREFERAPN_URI, new String[] { + "_id" + }, null, null, Telephony.Carriers.DEFAULT_SORT_ORDER); + if (cursor.getCount() > 0) { + cursor.moveToFirst(); + key = cursor.getString(ID_INDEX); + Log.v("settingActivty", "default apn key = " + key); + } + cursor.close(); + + // to find default proxy + String where = getOperatorNumericSelection(); + + cursor = getContentResolver().query(Telephony.Carriers.CONTENT_URI, new String[] { + "_id", "name", "apn", "type" + }, where, null, Telephony.Carriers.DEFAULT_SORT_ORDER); + + while (cursor != null && cursor.moveToNext()) { + String curKey = cursor.getString(cursor.getColumnIndex("_id")); + String curName = cursor.getString(cursor.getColumnIndex("name")); + if (curKey.equals(key)) { + Log.d("rtsp", "getDefaultApnName, find, key=" + curKey + ",curName=" + curName); + name = curName; + break; + } + } + + if (cursor != null) { + cursor.close(); + } + return name; + + } + + private String getSelectedApnKey() { + String key = null; + + Cursor cursor = getContentResolver().query(PREFERAPN_URI, new String[] { + "_id", "name" + }, null, null, null); + if (cursor.getCount() > 0) { + cursor.moveToFirst(); + key = cursor.getString(NAME_INDEX); + } + cursor.close(); + + Log.w("rtsp", "getSelectedApnKey key = " + key); + if (null == key) + return new String("null"); + return key; + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + case SELECT_APN: + setResult(resultCode); + finish(); + Log.w("rtsp", "onActivityResult requestCode = " + requestCode); + break; + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // TODO Auto-generated method stub + if (item.getItemId() == android.R.id.home) { + finish(); + } + return true; + } + + private String getOperatorNumericSelection() { + String[] mccmncs = getOperatorNumeric(); + String where; + where = (mccmncs[0] != null) ? "numeric=\"" + mccmncs[0] + "\"" : ""; + where += (mccmncs[1] != null) ? " or numeric=\"" + mccmncs[1] + "\"" : ""; + Log.d("SettingsActivity", "getOperatorNumericSelection: " + where); + return where; + } + + private String[] getOperatorNumeric() { + ArrayList<String> result = new ArrayList<String>(); + String mccMncFromSim = null; + if (mUseNvOperatorForEhrpd) { + String mccMncForEhrpd = SystemProperties.get("ro.cdma.home.operator.numeric", null); + if (mccMncForEhrpd != null && mccMncForEhrpd.length() > 0) { + result.add(mccMncForEhrpd); + } + } + + mccMncFromSim = TelephonyManager.getDefault().getSimOperator(); + + if (mccMncFromSim != null && mccMncFromSim.length() > 0) { + result.add(mccMncFromSim); + } + return result.toArray(new String[2]); + } +} diff --git a/src/org/codeaurora/gallery3d/video/StreamingHooker.java b/src/org/codeaurora/gallery3d/video/StreamingHooker.java new file mode 100644 index 000000000..d4fa43fc5 --- /dev/null +++ b/src/org/codeaurora/gallery3d/video/StreamingHooker.java @@ -0,0 +1,78 @@ +package org.codeaurora.gallery3d.video; + +import android.content.Intent; +import android.net.Uri; +import android.provider.Browser; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; + +import com.android.gallery3d.R; +import org.codeaurora.gallery3d.ext.MovieUtils; + +public class StreamingHooker extends MovieHooker { + private static final String TAG = "StreamingHooker"; + private static final boolean LOG = true; + + private static final String ACTION_STREAMING = "org.codeaurora.settings.streaming"; + private static final int MENU_INPUT_URL = 1; + private static final int MENU_SETTINGS = 2; + private static final int MENU_DETAIL = 3; + + public static final String KEY_LOGO_BITMAP = "logo-bitmap"; + + @Override + public boolean onCreateOptionsMenu(final Menu menu) { + super.onCreateOptionsMenu(menu); + // when in rtsp streaming type, generally it only has one uri. + menu.add(0, getMenuActivityId(MENU_INPUT_URL), 0, R.string.input_url); + menu.add(0, getMenuActivityId(MENU_SETTINGS), 0, R.string.streaming_settings); + return true; + } + + @Override + public boolean onPrepareOptionsMenu(final Menu menu) { + super.onPrepareOptionsMenu(menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(final MenuItem item) { + super.onOptionsItemSelected(item); + switch (getMenuOriginalId(item.getItemId())) { + case MENU_INPUT_URL: + gotoInputUrl(); + return true; + case MENU_SETTINGS: + gotoSettings(); + return true; + default: + return false; + } + } + + private void gotoInputUrl() { + final String appName = getClass().getName(); + final Intent intent = new Intent(); + intent.setAction(Intent.ACTION_VIEW); + intent.setData(Uri.parse("about:blank")); + intent.putExtra("inputUrl", true); + intent.putExtra(Browser.EXTRA_APPLICATION_ID, appName); + getContext().startActivity(intent); + if (LOG) { + Log.v(TAG, "gotoInputUrl() appName=" + appName); + } + } + + private void gotoSettings() { + final Intent intent = new Intent(ACTION_STREAMING); + // intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP + // | Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY); + // intent.putExtra(KEY_LOGO_BITMAP, + // getIntent().getParcelableExtra(KEY_LOGO_BITMAP)); + getContext().startActivity(intent); + if (LOG) { + Log.v(TAG, "gotoInputUrl()"); + } + } +} |