1. 程式人生 > >SimplifyReader原始碼學習:(一)音樂播放功能總結

SimplifyReader原始碼學習:(一)音樂播放功能總結

寫在前面:
SimplifyReader是我第一個用心研究原始碼的app,在這裡首先感謝開原始碼的分享者。 這也是我的第一篇部落格,希望可以記錄下來學習中的經驗和總結。也歡迎大家指正錯誤,共同進步。
最後,分享給大家最近讀到的兩句話:
我已亭亭,無憂亦無懼。
趁秋雨還未滴落,趁風霜還未曾侵蝕。

一、 概述

基本功能:開始播放、暫停播放、重新播放、播放下一首、播放前一首、跳轉進度播放、停止播放。
附加功能:頁面資訊更新、進度條顯示、相關動畫(needle和disc動畫)。

View:
MusicsFragment(實現了MusicsView介面)負責頁面顯示、響應使用者操作功能,一般呼叫Presenter的方法,具體處理邏輯在Presenter中實現。

Presenter:
MusicsPresenterImpl實現了MusicsPresenter、BaseMultiLoadedListener介面。它是View和Model的橋樑,進行一些邏輯處理。

Model:
Model主要提供業務資料。MusicsInteractorImpl的getMusicListData可以獲取音樂播放功能所需要的音樂資訊,並通過BaseMultiLoadedListener介面回撥資料給Presenter(MusicsPresenterImpl)。

二、 MediaPlayer介紹

音樂播放功能涉及的一個很重要的類就是MediaPlayer,其生命週期如下圖所示。當一個MediaPlayer物件被剛剛用new操作符建立或是呼叫了reset()方法後,它就處於Idle狀態。當呼叫了release()方法後,它就處於End狀態。這兩種狀態之間是MediaPlayer物件的生命週期。在SimplifyReader的MusicPlayer中使用了這個類。

MediaPlayer的生命週期可參考http://blog.csdn.net/ddna/article/details/5178864,我這裡只簡單介紹下在MusicPlayer中是如何使用的。
MusicPlayer實現了MediaPlayer的四個listener介面,覆寫了相關回調方法:
1)onCompletion():音樂播放完成時回撥,根據播放模式(單曲迴圈、順序播放、…)進行相應處理。
2)onPrepared():進入prepared狀態時的回撥方法。一般進行start()操作、廣播當前播放音樂的資訊和播放進度等。
3)onBufferingUpdate():網路流狀態改變時回撥方法。這裡一般廣播緩衝進度。View中接收廣播,實時更新音樂播放緩衝進度。(這裡原始碼中有錯誤,可改進。)
4)onError():播放錯誤時的回撥方法。

MediaPlayer生命週期

三、 動畫相關

SimplifyReader音樂播放介面仿網易雲音樂的播放介面,包括兩個動畫效果,唱針動畫和唱片動畫。其主要方法如下:

//開啟Needle(唱針)動畫
private void startNeedleAnimator() {
    if (isPlaying) {
        mNeedleAnimator = ObjectAnimator.ofFloat(mNeedle, "rotation", 0, NEEDLE_ROTATE_CIRCLE);//-30  表示逆時針旋轉30度
    } else {
        mNeedleAnimator = ObjectAnimator.ofFloat(mNeedle, "rotation", NEEDLE_ROTATE_CIRCLE, 0);//從-30度的位置旋轉到0度的位置
    }
    mNeedleAnimator.setDuration(NEEDLE_ANIMATOR_TIME);//350ms
    mNeedleAnimator.setInterpolator(new DecelerateInterpolator());//設定旋轉速率為減速模式
    if (mNeedleAnimator.isRunning() || mNeedleAnimator.isStarted()) {//取消之前正在或已經start而將要animate的animator
        mNeedleAnimator.cancel();
    }
    mNeedleAnimator.start();
}

//開啟中間圓盤(唱片)轉動
private void startDiscAnimator(float animatedValue) {
    mDiscLayoutAnimator = ObjectAnimator.ofFloat(mDiscLayout, "rotation", animatedValue, 360 + animatedValue);//順時針旋轉一週
    //對動畫的過程進行監聽
    mDiscLayoutAnimator.addUpdateListener(new AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator arg0) {
            mDiscLayoutAnimatorValue = (Float) arg0.getAnimatedValue();//獲得圓盤實時的旋轉角度
        }
    });
    mDiscLayoutAnimator.setDuration(DISC_ANIMATOR_TIME);
    mDiscLayoutAnimator.setRepeatCount(DISC_ANIMATOR_REPEAT_COUNT); //這裡設定為-1   表示不停止
    //設定旋轉速率  這裡為勻速效果
    mDiscLayoutAnimator.setInterpolator(new LinearInterpolator());

    if (mDiscLayoutAnimator.isRunning() || mDiscLayoutAnimator.isStarted()) {
        mDiscLayoutAnimator.cancel();
    }
    mDiscLayoutAnimator.start();
}

//中間圓盤(唱針)返回到最初的位置
private void reverseDiscAnimator() {
    mDiscLayoutAnimator = ObjectAnimator.ofFloat(mDiscLayout, "rotation", mDiscLayoutAnimatorValue, 360);//順時針回到原點
    mDiscLayoutAnimator.addUpdateListener(new AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator arg0) {
            mDiscLayoutAnimatorValue = (Float) arg0.getAnimatedValue();
        }
    });
    mDiscLayoutAnimator.setDuration(DISC_REVERSE_ANIMATOR_TIME);
    mDiscLayoutAnimator.setInterpolator(new AccelerateInterpolator());//加速模式
    if (mDiscLayoutAnimator.isRunning() || mDiscLayoutAnimator.isStarted()) {
        mDiscLayoutAnimator.cancel();
    }
    mDiscLayoutAnimator.start();
}

四、 MusicPlayService

MusicPlayService主要有兩個功能。
其一,監聽手機電話狀態,並通過EventBus通知UI執行緒。通過PhoneCallReceiver監聽手機外撥電話,PhoneStateChangedListener監聽來電狀態、摘機狀態和空閒狀態。
其二,響應音樂播放指令。通過PlayBroadCastReceiver接收View中廣播的音樂播放指令,並響應相關指令。而具體指令的實現則由MusicPlayer完成。

五、 圖片載入和高斯模糊處理

SimplifyReader音樂播放功能中Disc(圓盤部分)背景圖片的載入用到了ImageLoader的displayImage方法。這裡不詳細說明。
音樂播放介面背景圖片是對Disc背景圖片進行高斯模糊處理得到的。關鍵程式碼如下:

Bitmap bitmap = ImageBlurManager.doBlurJniArray(loadedImage, BLUR_RADIUS, false);
mBackgroundImage.setImageBitmap(bitmap);

而這裡的doBlurJniArray對應的程式碼如下:

public static Bitmap doBlurJniArray(Bitmap sentBitmap, int radius, boolean canReuseInBitmap) {
        Bitmap bitmap;
        if (canReuseInBitmap) {
            bitmap = sentBitmap;
        } else {
            //此處返回一個與原圖同畫素的圖,且畫素可修改
            bitmap = sentBitmap.copy(sentBitmap.getConfig(), true); 
        }
        if (radius < 1) {
            return (null);
        }
        int w = bitmap.getWidth();//返回點陣圖的寬度。
        int h = bitmap.getHeight();//返回點陣圖的高度值。
        int[] pix = new int[w * h];
        bitmap.getPixels(pix, 0, w, 0, 0, w, h);
        // Jni array calculate   此處通過jni呼叫C實現模糊效果  radius為模糊係數
        //所謂"模糊",可以理解成每一個畫素都取周邊畫素的平均值。 所以radius越大  模糊效果越好
        ImageBlur.blurIntArray(pix, w, h, radius);
        bitmap.setPixels(pix, 0, w, 0, 0, w, h);
        return (bitmap);
    }

六、 MusicsFragment主要方法說明:

1)onFirstUserVisible():
在onActivityCreated中呼叫,fragment第一次可見時載入。這裡主要是顯示緩衝進度條(注:此進度條在載入完成進入prepared狀態後經發送歌曲資訊的廣播呼叫refreshPageInfo而隱藏)和載入音樂列表。

2)onActivityCreated():
這裡主要註冊廣播接收器(音樂資訊廣播接收、播放進度廣播接收、緩衝進度廣播接收),開啟MusicPlayService服務。

3)onDetach():
這裡對所有的廣播接收器取消註冊(動態註冊的廣播接收器必須取消註冊),並呼叫onStopPlay()停止播放音樂。

4)initViewsAndEvents():
在onViewCreated()中呼叫,進行背景圖片初始化和控制元件Listener繫結等操作。

5)onEventComming():
接收來自MusicPlayService的EventBus通知,並根據手機電話狀態來開始或停止播放。

6)refreshMusicsList():
此方法在MusicsPresenter.loadListData()載入成功的回撥方法中呼叫(EVENT_REFRESH_DATA引數)。當首次進入音樂播放介面時載入成功會呼叫該方法。開始播放音樂。

7)addMoreMusicsList():
此方法在MusicsPresenter.loadListData()載入成功的回撥方法中呼叫(EVENT_LOAD_MORE_DATA引數)。一般當點選PlayNext按鈕並載入成功的時候呼叫。

8)rePlayMusic():暫停後重新播放。

9)startPlayMusic():開始播放。

10)pausePlayMusic():暫停播放。

11)stopPlayMusic():停止播放,一般在onDetach方法中呼叫。

12)playNextMusic():播放下一首。

13)playPrevMusic():播放前一首。

14)seekToPosition():跳轉至指定位置播放。

15)refreshPageInfo():
在prepared狀態,接收到音樂資訊的廣播後呼叫。用於頁面資訊更新,包括歌曲名、歌手名、背景圖片和Disc背景圖片的更新顯示。

16)refreshPlayProgress():
當接收到播放進度的廣播時呼叫,實時更新播放進度。

17)refreshPlaySecondProgress():
當接收到緩衝進度的廣播時呼叫,更新歌曲緩衝進度。

18)calss PlayBundleBroadCast
播放的音樂資訊的廣播接收器,在這裡呼叫refreshPageInfo()更新頁面資訊。一般在onPrepared()中傳送廣播。

19)class PlayPositionBroadCast
播放進度的廣播接收器,在這裡呼叫refreshPlayProgress()更新播放進度。一般在onPrepared()中傳送廣播(這裡的傳送廣播在Thread進行,每隔1s傳送一次)。

20)PlaySecondProgressBroadCast
緩衝進度的廣播接收器,在這裡呼叫refreshPlaySecondProgress()更新緩衝進度。一般在onBufferingUpdate中傳送廣播(這裡原始碼的方法待改進,具體見個人改進建議)。

七、 主要操作呼叫順序:

1)初始進入音樂播放介面:
說明:我這裡對原始碼進行了一些修改,增加或刪減了一些方法,大家只關注生命週期就好。
initViewsAndEvents() → onFirstUserVisible() → loadListData() → onActivityCreated() →
refreshMusicsList() → startPlayMusic() → pause() → onPrepared() → sendPlayBundle() → sendPlayCurPosition() → refreshPageInfo()
這裡的pause() 是因為首次進入頁面,須由使用者控制是否播放。但並不影響頁面更新。
這裡寫圖片描述

2)點選播放:
onClick: ctrBtn clicked → rePlayMusic() → 開啟動畫
這裡寫圖片描述

3)點選暫停:
onClick: ctrBtn clicked → pausePlayMusic() → 關閉動畫
這裡寫圖片描述

4)再次播放:
onClick: ctrBtn clicked → rePlayMusic() → 開啟動畫
這裡寫圖片描述

5)點選PlayNext:
click next button → addMoreMusicsList()→ refreshMusicsList → playNext() →prepareMusic() → onPrepared() → refreshPageInfo() → 開啟動畫
這裡寫圖片描述

6)點選PlayPrev:
click prev button → playPrevMusic()→ 停止動畫→ playPrev() →prepareMusic() → onPrepared() → refreshPageInfo() → 開啟動畫
這裡寫圖片描述

7)點選back鍵
onDetach() → stopPlayMusic()
這裡寫圖片描述

八、 個人改進建議:

1)關於音樂緩衝進度顯示:
原始碼中在onBufferingUpdate()回撥方法中當緩衝進度有更新時傳送廣播,在MusicsFragment中接收廣播,並進行進度更新。然而我發現app中並沒有顯示緩衝進度條。仔細檢視發現,onBufferingUpdate()回撥方法中的進度指的是佔歌曲全部時間的百分比(是0-100之間的數值),而進度更新PlayerSeekBar.setSecondaryProgress(progress)中的progress指的是實際的totalTime(以ms為單位,所以是一個很大的值)。因而會發生那樣的錯誤。以下為改進方法:

public void onBufferingUpdate(MediaPlayer mp, int percent) {
    if(percent<100){
        isbufferFinished = false;
        Intent intent = new Intent();
        intent.setAction(Constants.ACTION_MUSIC_SECOND_PROGRESS_BROADCAST);
        intent.putExtra(Constants.KEY_MUSIC_SECOND_PROGRESS, percent);
        context.sendBroadcast(intent);
   }else {//percent=100時只廣播一次
        if (!isbufferFinished){
            isbufferFinished = true;
            Intent intent = new Intent();
            intent.setAction(Constants.ACTION_MUSIC_SECOND_PROGRESS_BROADCAST);
            intent.putExtra(Constants.KEY_MUSIC_SECOND_PROGRESS, percent);
            context.sendBroadcast(intent);
        }
}

public void refreshPlaySecondProgress(int progress) {
    //mTotalDuration為總時間
    mPlayerSeekBar.setSecondaryProgress(progress*mTotalDuration/100);
    }

2)關於跳轉進度播放問題:
原始碼中雖然實現了seekTo()相關的函式,但跳轉功能並未實現。究其原因發現原始碼並沒有為seekBar繫結Listener,因而無法響應觸控跳轉。改進如下:

mPlayerSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
    @Override
    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
    }
    @Override
    public void onStartTrackingTouch(SeekBar seekBar) {
    }
    @Override
    public void onStopTrackingTouch(SeekBar seekBar) {
        mMusicsPresenter.seekTo(seekBar.getProgress()*100/mTotalDuration);
    }
});

3)關於初始進入音樂介面問題:
原始碼中,初始進入音樂介面即開始播放,個人認為是不符合使用者體驗的。改進的方法是,設定初始進入的標誌位,初始進入音樂介面時由使用者點選播放按鈕才能播放音樂。(當然,這裡初始進入更新頁面顯示資訊也是必須的,為了解決這個問題,我是讓初始進入的時候暫停播放,由使用者控制來重新播放)。

相關推薦

SimplifyReader原始碼學習音樂播放功能總結

寫在前面: SimplifyReader是我第一個用心研究原始碼的app,在這裡首先感謝開原始碼的分享者。 這也是我的第一篇部落格,希望可以記錄下來學習中的經驗和總結。也歡迎大家指正錯誤,共同進步。 最後,分享給大家

【Qt】通過QtCreator原始碼學習Qtpro檔案

1、學習目的 學習pro檔案的語法規則,這在跨平臺專案中會經常用到。和條件編譯相似,在pro中可以根據平臺選擇不同的編譯模組、檔案,還可以向原始碼中傳遞變數等。 2、學習方法 通過學習QtCreator原始碼中的pro檔案,來掌握pro檔案語法規則,下面以qtcreator.

【機器人學】機器人開源專案KDL原始碼學習8KDL的精髓

  首先說一下我的心得: 1. 我認為KDL的精髓是Spatial Vector,結合C++等面向物件的語言可以寫出較好的軟體。 2. 直接閱讀KDL程式碼不適合初學者學習機械臂動力學。 3. 要學習機械臂動力學的話應首先閱讀使用3維向量推導公式的文獻,也就是線速度和角速度獨立分析

【機器人學】機器人開源專案KDL原始碼學習4機械臂逆動力學的牛頓尤拉演算法

  機械臂的逆動力學問題可以認為是:已知機械臂各個連桿的關節的運動(關節位移、關節速度和關節加速度),求產生這個加速度響應所需要的力/力矩。KDL提供了兩個求解逆動力學的求解器,其中一個是牛頓尤拉法,這個方法是最簡單和高效的方法。    牛頓尤拉法演算法可以分為三個步驟: step1:

【機器人學】機器人開源專案KDL原始碼學習6笛卡爾空間軌跡規劃、圓弧過渡、姿態插值、梯形速度、pathlength

  本文的內容是對另一篇文章(連結)的補充,對Trajectory_example.cpp涉及到的原理作一些簡單的講解,主要內容是:   (1)機器人路徑規劃圓弧過渡的原理;   (2)機器人路徑規劃梯形波的原理;   (3)機器人末端姿態插值的方法(角-軸);   (4)KDL

【機器人學】機器人開源專案KDL原始碼學習7examples中的CMakeList.txt檔案解讀

通過學習KDL開源專案的程式碼可以學習CMake構建程式的知識,現簡單介紹一下orocos_kinematics_dynamics-master\orocos_kinematics_dynamics-master\orocos_kdl\examples\CMakeList.txt檔案的指令。

【機器人學】機器人開源專案KDL原始碼學習5KDL如何求解幾何雅克比矩陣

這篇文章試圖說清楚兩件事:1. 幾何雅克比矩陣的本質;2. KDL如何求解機械臂的幾何雅克比矩陣。 一、幾何雅克比矩陣的本質 機械臂的關節空間的速度可以對映到執行器末端在操作空間的速度,這種對映可以通過一個矩陣來描述,就是幾何雅克比矩陣,瞭解雅克比矩陣需要了解這種對映關係的本質,這

【機器人學】機器人開源專案KDL原始碼學習3機器人操作空間路徑規劃(Path Planning)和軌跡規劃Trajectory Planning示例

很多同學會把路徑規劃(Path Planning)和軌跡規劃(Trajectory Planning)這兩個概念混淆,路徑規劃只是表示了機械臂末端在操作空間中的幾何資訊,比如從工作臺的一端(A點)沿直線移動到另一端(B點)。而軌跡規劃則加上了時間律,比如它要完成的任務是從A點開始到B點結束,中間

Spring原始碼學習筆記 bean是怎麼生成的

bean 實在 bean 重新整理過程中產生的,首先我們看下 bean 的重新整理方法。下面是 AbstractApplicationContext 的 refresh 方法。 @Override public void refresh() throws

Java 7 原始碼學習系列——String

String表示字串,Java中所有字串的字面值都是String類的例項,例如“ABC”。字串是常量,在定義之後不能被改變,字串緩衝區支援可變的字串。因為 String 物件是不可變的,所以可以共享它們。例如: String str = "abc"; 相當於 char data[] =

【mxGraph】原始碼學習5mxGraph

由於mxGraph原始檔有一萬多行,且涉及很多其它原始檔,所以重點在於瞭解mxGraph的作用、結構以及定義了哪些方法 1. 概覽 1.1 作用 mxGraph繼承自mxEventSource以實現基於Web的圖形元件的功能性方面。要啟用平移和連線,使用se

Spring快取原始碼剖析工具選擇

  從本篇開始對Spring 4.3.6版本中Cache部分做一次深度剖析。剖析過程中會對其中使用到的設計模式以及原則進行分析。相信對設計內功修煉必定大有好處。 一、環境及工具   IntelliJ IDEA 2016.2   JDK 1.8       MacOS 二、測試用程式碼   目錄整體結構是這個樣

使用Keras進行深度學習Keras 入門

Keras是Python中以CNTK、Tensorflow或者Theano為計算後臺的一個深度學習建模環境。相對於其他深度學習的計算軟體,如:Tensorflow、Theano、Caffe等,Keras在實際應用中有一些顯著的優點,其中最主要的優點就是Ker

比特幣原始碼學習筆記

https://github.com/trottier/original-bitcoin 前言 從事區塊鏈的開發,不瞭解其底層核心技術是不夠的。許多人在看了比特幣白皮書之後仍然不清楚比特幣是怎樣實現的,因為比特幣的原始碼設計精巧,有許多設計白皮書未曾提及,加上本身

spark學習虛擬機器安裝及軟體要求

1 Linux虛擬機器的安裝 參考:https://blog.csdn.net/ProgrammingWay/article/details/78237856 採用的是VMWare,CentOS6.9。因為是在自己電腦上進行實驗,故架設三臺虛擬機器,分別命名為master(192.168.x

【mxGraph】原始碼學習7mxCell

1. 概覽 mxCell是graph model的元素。它們表示graph中的group、vertex和edge的狀態。 對於自定義屬性,建議使用XML節點作為cell的值。以下程式碼可用於建立具有XML節點的cell作為值: var doc = mxUtils

【Android】Android開源專案音樂播放原始碼彙總

作為一個有追求的程式設計師來說,專案原始碼必須看,但是網上那麼多資源是不讓你無從下手啊,博主今天為大家推薦五個經典專案吧。 一、android-UniversalMusicPlayer 這個開源專案展示瞭如何實現一個橫跨各種Android平臺的音樂播放器,包

【mxGraph】原始碼學習6mxGraphModel

1. 概覽 mxGraphModel繼承自mxEventSource以實現graph model。graph model是負責儲存graph資料結構的包裝器。graph model充當事務包裝器,其中包含所有更改的事件通知,而cell包含用於更新實際資料結構的原

Android 動畫原始碼學習

概述 Android提供了各種功能強大的應用動畫的使用者介面元素和繪製自定義的二維和三維圖形的應用。我們可以大致按照下面分類來學習理解 Animation 安卓框架提供了兩個動畫系統:屬性動畫和檢視動畫。屬性動畫在一般情況下,是首選的方法來使用,因為它更

springFramework 原始碼學習日記原始碼下載與編譯

 抽空學習springFramework原始碼,後續每天有時間都將學習心得發表,配合教程《spring 技術內幕》作者:計文柯出版社:機械工業出版社原始碼下載時間2012-9-2 下午4點,svn下載https://github.com/SpringSource/spr