編輯:Android開發實例
因為MediaPlayer的源碼比較多,所以只能來一個不完整分析了。
前段時間在工作中遇到一個問題就是MediaPlayer的播放網絡流媒體的時候,當沒有完全下載完畢的時,我們調用seekTo的時候會觸發OnCompletionListener,你們懂的咯,這樣就直接播放下一首了。挺糾結的,所以就決定看看mediaplayer的源碼咯,希望能從中找到解決的方法。
seekTo在MediaPlayer的調用流程如下圖:
seekTo 點擊上圖看大圖
在MediaPlayer.java中的seekTo是一個native修飾的方法
1: /**
2: * Seeks to specified time position.
3: *
4: * @param msec the offset in milliseconds from the start to seek to
5: * @throws IllegalStateException if the internal player engine has not been
6: * initialized
7: */
8: public native void seekTo(int msec) throws IllegalStateException;
好,我們來看看此方法的JNI是如何實現的。
1: static void android_media_MediaPlayer_seekTo(JNIEnv *env, jobject thiz, int msec)
2: {
3: sp<MediaPlayer> mp = getMediaPlayer(env, thiz);//獲取MediaPlayer實例
4: if (mp == NULL ) {
5: jniThrowException(env, "java/lang/IllegalStateException", NULL);
6: return;
7: }
8: LOGV("seekTo: %d(msec)", msec);
9: status_t result = mp->seekTo(msec);//1,調用MediaPlayer的seekTo方法
10: process_media_player_call( env, thiz, result, NULL, NULL );//2,處理MediaPlayer方法調用的返回結果
11: }
1,調用MediaPlayer的seekTo方法
1: status_t MediaPlayer::seekTo_l(int msec)
2: {
3: LOGV("seekTo %d", msec);
4: if ((mPlayer != 0) && ( mCurrentState & ( MEDIA_PLAYER_STARTED | MEDIA_PLAYER_PREPARED | MEDIA_PLAYER_PAUSED | MEDIA_PLAYER_PLAYBACK_COMPLETE) ) ) {
5: if ( msec < 0 ) {
6: LOGW("Attempt to seek to invalid position: %d", msec);
7: msec = 0;
8: } else if ((mDuration > 0) && (msec > mDuration)) {
9: LOGW("Attempt to seek to past end of file: request = %d, EOF = %d", msec, mDuration);
10: msec = mDuration;
11: }
12: // cache duration
13: mCurrentPosition = msec;
14: if (mSeekPosition < 0) {
15: getDuration_l(NULL);
16: mSeekPosition = msec;
17: //調用seekTo了
18: return mPlayer->seekTo(msec);
19: }
20: else {
21: LOGV("Seek in progress - queue up seekTo[%d]", msec);
22: return NO_ERROR;
23: }
24: }
25: LOGE("Attempt to perform seekTo in wrong state: mPlayer=%p, mCurrentState=%u", mPlayer.get(), mCurrentState);
26: return INVALID_OPERATION;
27: }
28:
29: status_t MediaPlayer::seekTo(int msec)
30: {
31: mLockThreadId = getThreadId();
32: Mutex::Autolock _l(mLock);
33: status_t result = seekTo_l(msec);
34: mLockThreadId = 0;
35: return result;
36: }
2,處理MediaPlayer方法調用的返回結果
1: static void process_media_player_call(JNIEnv *env, jobject thiz, status_t opStatus, const char* exception, const char *message)
2: {
3: if (exception == NULL) { // Don't throw exception. Instead, send an event.
4: if (opStatus != (status_t) OK) {
5: sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
6: if (mp != 0) mp->notify(MEDIA_ERROR, opStatus, 0);//調用MediaPlayer的notify
7: }
8: } else { // Throw exception!
9: if ( opStatus == (status_t) INVALID_OPERATION ) {
10: jniThrowException(env, "java/lang/IllegalStateException", NULL);
11: } else if ( opStatus != (status_t) OK ) {
12: if (strlen(message) > 230) {
13: // if the message is too long, don't bother displaying the status code
14: jniThrowException( env, exception, message);
15: } else {
16: char msg[256];
17: // append the status code to the message
18: sprintf(msg, "%s: status=0x%X", message, opStatus);
19: jniThrowException( env, exception, msg);
20: }
21: }
22: }
23: }
接下來看看MediaPlayer的notify方法,這個方法主要是通過判斷MediaPlayer的狀態向我們的app發送回調:
1: void MediaPlayer::notify(int msg, int ext1, int ext2)
2: {
3: LOGV("message received msg=%d, ext1=%d, ext2=%d", msg, ext1, ext2);
4: bool send = true;
5: bool locked = false;
6:
7: // TODO: In the future, we might be on the same thread if the app is
8: // running in the same process as the media server. In that case,
9: // this will deadlock.
10: //
11: // The threadId hack below works around this for the care of prepare
12: // and seekTo within the same process.
13: // FIXME: Remember, this is a hack, it's not even a hack that is applied
14: // consistently for all use-cases, this needs to be revisited.
15: if (mLockThreadId != getThreadId()) {
16: mLock.lock();
17: locked = true;
18: }
19:
20: if (mPlayer == 0) {
21: LOGV("notify(%d, %d, %d) callback on disconnected mediaplayer", msg, ext1, ext2);
22: if (locked) mLock.unlock(); // release the lock when done.
23: return;
24: }
25:
26: switch (msg) {
27: case MEDIA_NOP: // interface test message
28: break;
29: case MEDIA_PREPARED://prepared結束
30: LOGV("prepared");
31: mCurrentState = MEDIA_PLAYER_PREPARED;
32: if (mPrepareSync) {
33: LOGV("signal application thread");
34: mPrepareSync = false;
35: mPrepareStatus = NO_ERROR;
36: mSignal.signal();
37: }
38: break;
39: case MEDIA_PLAYBACK_COMPLETE://播放完畢
40: LOGV("playback complete");
41: if (!mLoop) {
42: mCurrentState = MEDIA_PLAYER_PLAYBACK_COMPLETE;
43: }
44: break;
45: case MEDIA_ERROR://出錯
46: // Always log errors.
47: // ext1: Media framework error code.
48: // ext2: Implementation dependant error code.
49: LOGE("error (%d, %d)", ext1, ext2);
50: mCurrentState = MEDIA_PLAYER_STATE_ERROR;
51: if (mPrepareSync)
52: {
53: LOGV("signal application thread");
54: mPrepareSync = false;
55: mPrepareStatus = ext1;
56: mSignal.signal();
57: send = false;
58: }
59: break;
60: case MEDIA_INFO://logcat經常可以看到
61: // ext1: Media framework error code.
62: // ext2: Implementation dependant error code.
63: LOGW("info/warning (%d, %d)", ext1, ext2);
64: break;
65: case MEDIA_SEEK_COMPLETE://seek完畢
66: LOGV("Received seek complete");
67: if (mSeekPosition != mCurrentPosition) {
68: LOGV("Executing queued seekTo(%d)", mSeekPosition);
69: mSeekPosition = -1;
70: seekTo_l(mCurrentPosition);
71: }
72: else {
73: LOGV("All seeks complete - return to regularly scheduled program");
74: mCurrentPosition = mSeekPosition = -1;
75: }
76: break;
77: case MEDIA_BUFFERING_UPDATE://緩沖
78: LOGV("buffering %d", ext1);
79: break;
80: case MEDIA_SET_VIDEO_SIZE://設置視頻大小
81: LOGV("New video size %d x %d", ext1, ext2);
82: mVideoWidth = ext1;
83: mVideoHeight = ext2;
84: break;
85: default:
86: LOGV("unrecognized message: (%d, %d, %d)", msg, ext1, ext2);
87: break;
88: }
89:
90: sp<MediaPlayerListener> listener = mListener;
91: if (locked) mLock.unlock();
92:
93: // this prevents re-entrant calls into client code
94: if ((listener != 0) && send) {
95: Mutex::Autolock _l(mNotifyLock);
96: LOGV("callback application");
97: //調用監聽器,回調應用的監聽器
98: listener->notify(msg, ext1, ext2);
99: LOGV("back from callback");
100: }
101: }
在監聽器的notify方法中,是通過jni“反向調用”MediaPlayer.java中的postEventFromNative,在通過mEventHandler根據不同的消息類型調用不同的監聽器。
1: private static void postEventFromNative(Object mediaplayer_ref,
2: int what, int arg1, int arg2, Object obj)
3: {
4: MediaPlayer mp = (MediaPlayer)((WeakReference)mediaplayer_ref).get();
5: if (mp == null) {
6: return;
7: }
8:
9: if (mp.mEventHandler != null) {
10: Message m = mp.mEventHandler.obtainMessage(what, arg1, arg2, obj);
11: mp.mEventHandler.sendMessage(m);
12: }
13: }
OK,至此我們分析了seekTo的整個流程。其他方法的流程是很相似的,大家不妨親自去看看。
回到本文開頭的問題,通過觀察logcat得知是發生錯誤導致的,暫時未找到解決的辦法。如果你有解決方法不妨告訴我哦。
------------------------
03-20 03:08:13.302: ERROR/MediaPlayer(419): Attempt to call getDuration without a valid mediaplayer
03-20 03:08:13.365: ERROR/MediaPlayer(419): error (-38, 0)
03-20 03:08:13.372: ERROR/MediaPlayer(419): Attempt to call getDuration without a valid mediaplayer
03-20 03:08:13.512: ERROR/MediaPlayer(419): error (-38, 0)
03-20 03:08:13.522: DEBUG/dalvikvm(419): GC freed 3405 objects / 831072 bytes in 122ms
03-20 03:08:13.752: ERROR/MediaPlayer(419): Error (-38,0)
03-20 03:08:13.762: DEBUG/MultiPlayer(419): Error: -38,0
03-20 03:08:13.772: ERROR/MediaPlayer(419): Error (-38,0)
03-20 03:08:13.781: ERROR/MediaPlayer(419): pause called in state 0
03-20 03:08:13.781: ERROR/MediaPlayer(419): error (-38, 0)
-------------------------
PS:“反向調用”就是在c/c++中通過jni來調用java中的方法或屬性。
就目前的互聯網發展來看,已經有越來越多互聯網企業都在Android平台上部署其客戶端,並且為了提升用戶體驗,這些客戶端都做得布局合理而且美觀。本文所要介紹的And
登錄應用程序的屏幕,詢問憑據登錄到一些特定的應用。可能需要登錄到Facebook,微博等本章介紹了,如何創建一個登錄界面,以及如何管理安全問題和錯誤嘗試。首先,必須定義兩
SlidingDrawer隱藏屏外的內容,並允許用戶通過handle以顯示隱藏內容。它可以垂直或水平滑動,它有倆個View組成,其一是可以拖動的handle,其二
對於很多初學者或者剛工作的Android新手來說,我們的項目經驗還停留在做demo的階段,有沒有一種很low的感覺,並且當你真正上手做大項目的時候又