Android MediaPlayer播放通过有线耳机而不是通过蓝牙进行

时间:2015-10-13 10:26:30

标签: java android bluetooth

我有一个简单的音乐播放器应用程序(source),当使用耳机时,它在Lollipop中有播放问题。音乐将在30秒到5分钟之间正常播放,然后暂停~2-4秒,然后恢复。

这种行为似乎通常在屏幕关闭时发生,但获取CPU唤醒锁并没有帮助。

暂停的频率似乎随着时间的推移而加速。起初它每小时一次,但是暂停之间的时间每次减少大约一半,直到它几乎每分钟都暂停。

我用iTunes编码的aac文件观察到了这种行为,其他人用mp3录制了它。

仅在通过有线耳机播放时观察到此情况。我从来没有在蓝牙耳机上遇到过这种情况。

可能导致这种情况的原因是什么?这似乎是一个流程优先问题,但我不知道如何解决这类问题。

我还没有在Android 4.x上体验到这一点。

这是此问题的Github ticket

以下是源代码的一些相关内容:

清单

<?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);
        }
    }
}

1 个答案:

答案 0 :(得分:2)

我从really helpful response的开发者那里得到了Vanilla Music Player

我们使用一个单独的线程来预读当前正在播放的文件:

- &GT;该线程以大约256kb / s的速度读取文件,因此它将比mediaserver更快地读取文件 - &GT;这使文件非常好地保留在页面/磁盘缓存中 - &GT; ..这样可以最大限度地减少由于时髦的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);
...

自实施此更改以来,我没有遇到过这个问题。