簡體   English   中英

Android MediaPlayer播放通過有線耳機而不是通過藍牙進行

[英]Android MediaPlayer playback stutters over wired headphones, not over Bluetooth

我有一個簡單的音樂播放器應用程序( ),當使用耳機時,它在Lollipop中有播放問題。 音樂將正常播放30秒至5分鍾,然后暫停約2-4秒,然后恢復。

這種行為似乎通常在屏幕關閉時發生,但獲取CPU喚醒鎖並沒有幫助。

暫停的頻率似乎隨着時間的推移而加速。 起初它每小時一次,但是暫停之間的時間每次減少大約一半,直到它幾乎每分鍾都暫停。

我用iTunes編碼的aac文件觀察到了這種行為,其他人用mp3錄制了它。

僅在通過有線耳機播放時才會觀察到此情況。 我從未在藍牙耳機上遇到過這種情況。

可能是什么導致了這個? 這似乎是一個流程優先問題,但我不知道如何解決這類問題。

我沒有在Android 4.x上體驗過這個。

這是這個問題的Github票

以下是一些相關的源代碼:

表現

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.smithdtyler.prettygoodmusicplayer"
    android:versionCode="65"
    android:versionName="3.2.14" >

    <uses-sdk
        android:minSdkVersion="16"
        android:targetSdkVersion="19" />

    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_pgmp_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppBaseTheme" >

        <!-- Set the artist list to launch mode single task to prevent multiple instances -->
        <!-- This fixes an error where exiting the application just brings up another instance -->
        <!-- See https://developer.android.com/guide/topics/manifest/activity-element.html#lmode -->
        <activity
            android:name="com.smithdtyler.prettygoodmusicplayer.ArtistList"
            android:label="@string/app_name"
            android:launchMode="singleTask" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
                <category android:name="android.intent.category.CATEGORY_APP_MUSIC " />
            </intent-filter>
        </activity>
        <activity
            android:name="com.smithdtyler.prettygoodmusicplayer.SettingsActivity"
            android:label="@string/title_activity_settings" >
        </activity>
        <activity
            android:name="com.smithdtyler.prettygoodmusicplayer.AlbumList"
            android:label="@string/title_activity_album_list" >
        </activity>
        <activity
            android:name="com.smithdtyler.prettygoodmusicplayer.SongList"
            android:label="@string/title_activity_song_list" >
        </activity>
        <activity
            android:name="com.smithdtyler.prettygoodmusicplayer.NowPlaying"
            android:exported="true"
            android:label="@string/title_activity_now_playing" >
        </activity>
        <!--
        The service has android:exported="true" because that's needed for
        control from the notification. Not sure why it causes a warning...
        -->
        <service
            android:name="com.smithdtyler.prettygoodmusicplayer.MusicPlaybackService"
            android:exported="true"
            android:icon="@drawable/ic_pgmp_launcher" >
        </service>

        <receiver
            android:name="com.smithdtyler.prettygoodmusicplayer.MusicBroadcastReceiver"
            android:enabled="true" >
            <intent-filter android:priority="2147483647" >
                <action android:name="android.intent.action.MEDIA_BUTTON" />
            </intent-filter>
        </receiver>
    </application>

</manifest>

MusicPlaybackService.onCreate()

@Override
public synchronized void onCreate() {
    Log.i(TAG, "Music Playback Service Created!");
    isRunning = true;
    sharedPref = PreferenceManager.getDefaultSharedPreferences(this);

    powerManager =(PowerManager) getSystemService(POWER_SERVICE);
    wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
            "PGMPWakeLock");

    random = new Random();

    mp = new MediaPlayer();

    mp.setOnCompletionListener(new OnCompletionListener() {

        @Override
        public void onCompletion(MediaPlayer mp) {
            Log.i(TAG, "Song complete");
            next();
        }

    });

    // https://developer.android.com/training/managing-audio/audio-focus.html
    audioFocusListener = new PrettyGoodAudioFocusChangeListener();

    // Get permission to play audio
    am = (AudioManager) getBaseContext().getSystemService(
            Context.AUDIO_SERVICE);

    HandlerThread thread = new HandlerThread("ServiceStartArguments");
    thread.start();

    // Get the HandlerThread's Looper and use it for our Handler
    mServiceLooper = thread.getLooper();
    mServiceHandler = new ServiceHandler(mServiceLooper);

    // https://stackoverflow.com/questions/19474116/the-constructor-notification-is-deprecated
    // https://stackoverflow.com/questions/6406730/updating-an-ongoing-notification-quietly/15538209#15538209
    Intent resultIntent = new Intent(this, NowPlaying.class);
    resultIntent.putExtra("From_Notification", true);
    resultIntent.putExtra(AlbumList.ALBUM_NAME, album);
    resultIntent.putExtra(ArtistList.ARTIST_NAME, artist);
    resultIntent.putExtra(ArtistList.ARTIST_ABS_PATH_NAME, artistAbsPath);

    // Use the FLAG_ACTIVITY_CLEAR_TOP to prevent launching a second
    // NowPlaying if one already exists.
    resultIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
    PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
            resultIntent, 0);

    Builder builder = new NotificationCompat.Builder(
            this.getApplicationContext());

    String contentText = getResources().getString(R.string.ticker_text);
    if (songFile != null) {
        contentText = Utils.getPrettySongName(songFile);
    }

    Notification notification = builder
            .setContentText(contentText)
            .setSmallIcon(R.drawable.ic_pgmp_launcher)
            .setWhen(System.currentTimeMillis())
            .setContentIntent(pendingIntent)
            .setContentTitle(
                    getResources().getString(R.string.notification_title))
                    .build();

    startForeground(uniqueid, notification);

    timer = new Timer();
    timer.scheduleAtFixedRate(new TimerTask() {
        public void run() {
            onTimerTick();
        }
    }, 0, 500L);

    Log.i(TAG, "Registering event receiver");
    mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
    // Apparently audio registration is persistent across lots of things...
    // restarts, installs, etc.
    mAudioManager.registerMediaButtonEventReceiver(cn);
    // I tried to register this in the manifest, but it doesn't seen to
    // accept it, so I'll do it this way.
    getApplicationContext().registerReceiver(receiver, filter);

    headphoneReceiver = new HeadphoneBroadcastReceiver();
    IntentFilter intentFilter = new IntentFilter();
    intentFilter.addAction("android.intent.action.HEADSET_PLUG");
    registerReceiver(headphoneReceiver, filter);
}

MusicPlaybackService.startPlayingFile()

private synchronized void startPlayingFile(int songProgress) {
    // Have we loaded a file yet?
    if (mp.getDuration() > 0) {
        pause();
        mp.stop();
        mp.reset();
    }

    // open the file, pass it into the mp
    try {
        fis = new FileInputStream(songFile);
        mp.setDataSource(fis.getFD());
        mp.prepare();
        if(songProgress > 0){
            mp.seekTo(songProgress);
        }
        wakeLock.acquire();
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IllegalArgumentException e) {
        e.printStackTrace();
    } catch (IllegalStateException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

MusicPlaybackService計時器任務

private void onTimerTick() {
    long currentTime = System.currentTimeMillis();
    if (pauseTime < currentTime) {
        pause();
    }
    updateResumePosition();
    sendUpdateToClients();
}

private void updateResumePosition(){
    long currentTime = System.currentTimeMillis();
    if(currentTime - 10000 > lastResumeUpdateTime){
        if(mp != null && songFile != null && mp.isPlaying()){
            int pos = mp.getCurrentPosition();
            SharedPreferences prefs = getSharedPreferences("PrettyGoodMusicPlayer", MODE_PRIVATE);
            Log.i(TAG,
                    "Preferences update success: "
                            + prefs.edit()
                            .putString(songFile.getParentFile().getAbsolutePath(),songFile.getName() + "~" + pos)
                            .commit());
        }
        lastResumeUpdateTime = currentTime;
    }
}


private void sendUpdateToClients() {
    List<Messenger> toRemove = new ArrayList<Messenger>();
    synchronized (mClients) {
        for (Messenger client : mClients) {
            Message msg = Message.obtain(null, MSG_SERVICE_STATUS);
            Bundle b = new Bundle();
            if (songFile != null) {
                b.putString(PRETTY_SONG_NAME,
                        Utils.getPrettySongName(songFile));
                b.putString(PRETTY_ALBUM_NAME, songFile.getParentFile()
                        .getName());
                b.putString(PRETTY_ARTIST_NAME, songFile.getParentFile()
                        .getParentFile().getName());
            } else {
                // songFile can be null while we're shutting down.
                b.putString(PRETTY_SONG_NAME, " ");
                b.putString(PRETTY_ALBUM_NAME, " ");
                b.putString(PRETTY_ARTIST_NAME, " ");
            }

            b.putBoolean(IS_SHUFFLING, this._shuffle);

            if (mp.isPlaying()) {
                b.putInt(PLAYBACK_STATE, PlaybackState.PLAYING.ordinal());
            } else {
                b.putInt(PLAYBACK_STATE, PlaybackState.PAUSED.ordinal());
            }
            // We might not be able to send the position right away if mp is
            // still being created
            // so instead let's send the last position we knew about.
            if (mp.isPlaying()) {
                lastDuration = mp.getDuration();
                lastPosition = mp.getCurrentPosition();
            }
            b.putInt(TRACK_DURATION, lastDuration);
            b.putInt(TRACK_POSITION, lastPosition);
            msg.setData(b);
            try {
                client.send(msg);
            } catch (RemoteException e) {
                e.printStackTrace();
                toRemove.add(client);
            }
        }

        for (Messenger remove : toRemove) {
            mClients.remove(remove);
        }
    }
}

我從Vanilla音樂播放器的開發者那里得到了非常有用的回復

我們使用一個單獨的線程來預讀當前播放的文件:

- >線程以大約256kb / s的速度讀取文件,因此它將比mediaserver更快地讀取文件 - >這使文件很有可能保留在頁面/磁盤緩存中 - > ..這樣可以最大限度地減少機會由於時髦的SD卡或其他IO暫停導致的“輟學”。

代碼位於: https//github.com/vanilla-music/vanilla/blob/master/src/ch/blinkenlights/android/vanilla/ReadaheadThread.java該代碼不依賴於香草音樂的任何部分:如果你想嘗試一下,只需將它放入你的項目中並執行以下操作:

onCreate {
 ...
 mReadaheadThread = new ReadaheadThread()
 ...
}
...
mMediaPlayer.setDataSource(path);
mReadaheadThread.setDataSource(path);
...

自從實施此更改以來,我沒有遇到過這個問題。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM