代码之家  ›  专栏  ›  技术社区  ›  transiti0nary

如何为带有交互式通知的网络广播流创建Android前台服务?

  •  5
  • transiti0nary  · 技术社区  · 7 年前

    我正在尝试构建一个极其简单的广播流应用程序,它存储一个web广播URL列表,可以选择该列表来流式传输音频;使用服务允许在应用程序未激活时继续播放+控制来自通知。

    我需要的控件非常简单:播放/暂停和停止,这将终止服务,并在清除通知或按下应用程序中的停止按钮时引发。

    我为大量的代码表示歉意,但这就是我的立场:

    public class StreamingService extends Service
            implements MediaPlayer.OnPreparedListener, MediaPlayer.OnErrorListener {
    
        // .. snipped out fields
    
        private AudioManager.OnAudioFocusChangeListener mOnAudioFocusChangeListener =
                new AudioManager.OnAudioFocusChangeListener() {
                    @Override
                    public void onAudioFocusChange(int focusChange) {
                        switch (focusChange) {
                            case AudioManager.AUDIOFOCUS_GAIN:
                                // set mCurrentAudioFocusState field
                        }
    
                        if (mMediaPlayer != null)
                            configurePlayerState();
                    }
                };
    
        private int mCurrentAudioFocusState = AUDIO_NO_FOCUS_NO_DUCK;
    
        private final IntentFilter mAudioNoisyIntentFilter =
                new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
    
        private BroadcastReceiver mNoisyReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                // Pause when headphones unplugged
                mMediaPlayer.pause();
            }
        };
    
        private boolean mAudioNoisyReceiverRegistered = false;
    
        @Override
        public void onCreate() {
            super.onCreate();
    
            AudioManager mAudioManager = (AudioManager)
                    getSystemService(Context.AUDIO_SERVICE);
    
            int result = mAudioManager.requestAudioFocus(
                    mOnAudioFocusChangeListener,
                    AudioManager.STREAM_MUSIC,
                    AudioManager.AUDIOFOCUS_GAIN
            );
    
            if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
                stopSelf();
            } else {
                mCurrentAudioFocusState = AUDIO_FOCUSED;
            }
        }
    
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            mMediaPlayer = new MediaPlayer();
            mMediaPlayer.setOnPreparedListener(this);
            mMediaPlayer.setOnErrorListener(this);
            mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
            mMediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
    
            WifiManager.WifiLock wifiLock =
                    ((WifiManager) Objects.requireNonNull(
                            getApplicationContext().getSystemService(Context.WIFI_SERVICE)))
                            .createWifiLock(WifiManager.WIFI_MODE_FULL, "wifi_lock");
            wifiLock.acquire();
    
            try {
                mMediaPlayer.setDataSource(intent.getStringExtra(STREAM_URI));
            } catch (IOException e) {
                e.printStackTrace();
            }
    
            mMediaPlayer.prepareAsync();
            onStartIntent = intent;
    
            return Service.START_STICKY;
        }
    
        @Override
        public void onDestroy() {
            mMediaPlayer.release();
        }
    
        @Nullable
        @Override
        public IBinder onBind(Intent intent) {
            return null;
        }
    
        @Override
        public boolean onError(MediaPlayer mediaPlayer, int i, int i1) {
            mMediaPlayer.reset();
            return true;
        }
    
        @Override
        public void onPrepared(MediaPlayer mediaPlayer) {
            handleIntent(onStartIntent);
        }
    
        private void handleIntent(Intent intent) {
            String action = intent.getAction();
            String command = intent.getStringExtra(CMD_NAME);
    
            if (ACTION_CMD.equals(action)) {
                switch (command) {
                    case CMD_PLAY:
                        registerAudioNoisyReceiver();
                        mMediaPlayer.start();
                        startForeground(NOTIFICATION_ID, buildNotification());
                    case CMD_PAUSE:
                        unregisterAudioNoisyReceiver();
                        mMediaPlayer.pause();
                        startForeground(NOTIFICATION_ID, buildNotification());
                    case CMD_STOP:
                        unregisterAudioNoisyReceiver();
                        mMediaPlayer.stop();
                        stopSelf();
                }
            }
    
        }
    
        private Notification buildNotification() {
            createNotificationChannel();
    
            NotificationCompat.Builder builder =
                    new NotificationCompat.Builder(getApplicationContext(), NOTIFICATION_CHANNEL);
    
            builder
                    .setContentTitle(onStartIntent.getStringExtra(STREAM_TITLE))
                    .setContentIntent(PendingIntent.getActivity(
                            this,
                            0,
                            new Intent(getApplicationContext(), MainActivity.class),
                            0))
                    .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
                    .setDeleteIntent(getActionIntent(CMD_STOP));
    
            builder
                    .setSmallIcon(android.R.drawable.ic_media_play)
                    .setColor(ContextCompat.getColor(this, R.color.colorPrimaryDark));
    
            builder
                    .addAction(new NotificationCompat.Action(
                            android.R.drawable.ic_media_pause, getString(R.string.pause),
                            getActionIntent(CMD_PAUSE)));
    
            builder
                    .setStyle(new android.support.v4.media.app.NotificationCompat.MediaStyle()
                            .setShowActionsInCompactView(0)
                            .setShowCancelButton(true)
                            .setCancelButtonIntent(
                                    getActionIntent(CMD_STOP)));
    
            return builder.build();
        }
    
        private PendingIntent getActionIntent(String action) {
            Intent s = new Intent(getApplicationContext(), StreamingService.class);
            s.putExtra(
                    STREAM_TITLE,
                    onStartIntent.getStringExtra(STREAM_TITLE)
            );
    
            s.putExtra(
                    STREAM_URI,
                    onStartIntent.getStringExtra(STREAM_URI)
            );
    
            s.setAction(ACTION_CMD);
    
            s.putExtra(
                    CMD_NAME,
                    action
            );
    
            s.setPackage(getApplicationContext().getPackageName());
    
            return PendingIntent.getService(
                    getApplicationContext(), 0, s, 0);
        }
    
        // snipped methods to register and unregister noisy receiver
    
        private void configurePlayerState() {
            switch(mCurrentAudioFocusState) {
                case AUDIO_NO_FOCUS_CAN_DUCK:
                    registerAudioNoisyReceiver();
                    mMediaPlayer.setVolume(VOLUME_DUCK, VOLUME_DUCK);
                case AUDIO_NO_FOCUS_LOST:
                    unregisterAudioNoisyReceiver();
                    mMediaPlayer.stop();
                case AUDIO_NO_FOCUS_NO_DUCK:
                    unregisterAudioNoisyReceiver();
                    mMediaPlayer.pause();
                case AUDIO_FOCUSED:
                    registerAudioNoisyReceiver();
                    mMediaPlayer.setVolume(VOLUME_NORMAL, VOLUME_NORMAL);
            }
        }
    
        private void createNotificationChannel() {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                CharSequence name = getString(R.string.channel_name);
                String description = getString(R.string.channel_description);
                int importance = NotificationManager.IMPORTANCE_DEFAULT;
                NotificationChannel channel =
                        new NotificationChannel(NOTIFICATION_CHANNEL, name, importance);
                channel.setDescription(description);
    
                NotificationManager notificationManager = getSystemService(NotificationManager.class);
                assert notificationManager != null;
                notificationManager.createNotificationChannel(channel);
            }
        }
    
    
    }
    

    这是根据谷歌的媒体播放讲座、Android文档以及UAMP等示例应用程序和其他在线示例整理出来的。

    目前的代码是:启动,似乎设置了音频,但随后似乎暂停、停止和销毁,也销毁了通知。应用程序中不会出现任何通知,也不会播放音频。下面是日志:

    05-06 12:41:21.407  1903  1994 I ActivityManager: Displayed com.ojm.pinstream/.activities.MainActivity: +727ms
    05-06 12:41:23.955  1903  2517 D AudioService: Stream muted, skip playback
    05-06 12:41:23.962  1903  3205 I ActivityManager: START u0 {cmp=com.ojm.pinstream/.activities.PlayActivity} from uid 10191
    05-06 12:41:23.979 12786 12786 W AudioManager: Use of stream types is deprecated for operations other than volume control
    05-06 12:41:23.979 12786 12786 W AudioManager: See the documentation of requestAudioFocus() for what to use instead with android.media.AudioAttributes to qualify your playback use case
    05-06 12:41:23.980  1903  3205 I MediaFocusControl: requestAudioFocus() from uid/pid 10191/12786 clientId=android.media.AudioManager@6badb4bcom.ojm.pinstream.services.StreamingService$1@3626928 callingPack=com.ojm.pinstream req=1 flags=0x0 sdk=27
    05-06 12:41:23.986 12786 12786 W MediaPlayer: Use of stream types is deprecated for operations other than volume control
    05-06 12:41:23.986 12786 12786 W MediaPlayer: See the documentation of setAudioStreamType() for what to use instead with android.media.AudioAttributes to qualify your playback use case
    05-06 12:41:23.990 12786 12786 V MediaHTTPService: MediaHTTPService(android.media.MediaHTTPService@9e12641): Cookies: null
    05-06 12:41:23.992  1808 25066 D NuPlayerDriver: NuPlayerDriver(0xe8513800) created, clientPid(12786)
    05-06 12:41:23.996 12786 12808 V MediaHTTPService: makeHTTPConnection: CookieManager created: java.net.CookieManager@5cb47e6
    05-06 12:41:23.997 12786 12808 V MediaHTTPService: makeHTTPConnection(android.media.MediaHTTPService@9e12641): cookieHandler: java.net.CookieManager@5cb47e6 Cookies: null
    05-06 12:41:24.005 12786 12808 D NetworkSecurityConfig: No Network Security Config specified, using platform default
    05-06 12:41:24.053  1903  4685 E NotificationService: Suppressing notification from package by user request.
    05-06 12:41:24.056  1903  1966 E NotificationService: Suppressing notification from package by user request.
    05-06 12:41:24.076 12786 12791 I zygote64: Do partial code cache collection, code=60KB, data=45KB
    05-06 12:41:24.076 12786 12791 I zygote64: After code cache collection, code=60KB, data=45KB
    05-06 12:41:24.078 12786 12791 I zygote64: Increasing code cache capacity to 256KB
    05-06 12:41:24.203  1903  1994 I ActivityManager: Displayed com.ojm.pinstream/.activities.PlayActivity: +203ms
    05-06 12:41:24.227 12786 12807 D OpenGLRenderer: endAllActiveAnimators on 0x7bd8b64c00 (ListView) with handle 0x7be64b8340
    05-06 12:41:27.025  1903  8861 E NotificationService: Suppressing notification from package by user request.
    05-06 12:41:27.031  1903  1966 E NotificationService: Suppressing notification from package by user request.
    05-06 12:41:28.257  5051  5051 V ApiRequest: Performing request to https://127.0.0.1:8384/rest/events?since=0&limit=1
    05-06 12:41:28.322  5051  5051 D EventProcessor: Reading events starting with id 1675
    05-06 12:41:28.322  5051  5051 V ApiRequest: Performing request to https://127.0.0.1:8384/rest/events?since=1675&limit=0
    05-06 12:41:28.733  1903  8861 D WificondControl: Scan result ready event
    05-06 12:41:29.020  1808 12827 D GenericSource: stopBufferingIfNecessary_l, mBuffering=0
    05-06 12:41:29.020  1808 12818 D NuPlayerDriver: notifyListener_l(0xe8513800), (1, 0, 0, -1), loop setting(0, 0)
    05-06 12:41:29.039  1903  3205 V MediaRouterService: restoreBluetoothA2dp(false)
    05-06 12:41:29.039  1711  6225 D AudioPolicyManagerCustom: setForceUse() usage 1, config 10, mPhoneState 0
    05-06 12:41:29.040  1808  2811 D NuPlayerDriver: start(0xe8513800), state is 4, eos is 0
    05-06 12:41:29.041  1808 12818 I GenericSource: start
    05-06 12:41:29.061  1808 12834 I OMXClient: Treble IOmx obtained
    05-06 12:41:29.061  1812  1902 I OMXMaster: makeComponentInstance(OMX.google.mp3.decoder) in omx@1.0-service process
    05-06 12:41:29.067  1812  1902 E OMXNodeInstance: setConfig(0xf362a720:google.mp3.decoder, ConfigPriority(0x6f800002)) ERROR: Undefined(0x80001001)
    05-06 12:41:29.068  1808 12834 I ACodec  : codec does not support config priority (err -2147483648)
    05-06 12:41:29.068  1812  6179 E OMXNodeInstance: getConfig(0xf362a720:google.mp3.decoder, ConfigAndroidVendorExtension(0x6f100004)) ERROR: Undefined(0x80001001)
    05-06 12:41:29.069  1808 12834 I MediaCodec: MediaCodec will operate in async mode
    05-06 12:41:29.081  1808  2811 D NuPlayerDriver: pause(0xe8513800)
    05-06 12:41:29.081  1808  2811 D NuPlayerDriver: notifyListener_l(0xe8513800), (7, 0, 0, -1), loop setting(0, 0)
    05-06 12:41:29.082  1903  1966 E NotificationService: Suppressing notification from package by user request.
    05-06 12:41:29.082  1903  8861 V MediaRouterService: restoreBluetoothA2dp(false)
    05-06 12:41:29.084  1711  6225 D AudioPolicyManagerCustom: setForceUse() usage 1, config 10, mPhoneState 0
    05-06 12:41:29.097  1808  2811 D NuPlayerDriver: stop(0xe8513800)
    05-06 12:41:29.097  1808  2811 D NuPlayerDriver: notifyListener_l(0xe8513800), (8, 0, 0, -1), loop setting(0, 0)
    05-06 12:41:29.101 12786 12786 V MediaPlayer: resetDrmState:  mDrmInfo=null mDrmProvisioningThread=null mPrepareDrmInProgress=false mActiveDrmScheme=false
    05-06 12:41:29.102 12786 12786 V MediaPlayer: cleanDrmObj: mDrmObj=null mDrmSessionId=null
    05-06 12:41:29.102  1808  2811 D NuPlayerDriver: reset(0xe8513800) at state 8
    05-06 12:41:29.103  1903  1903 I NotificationService: Cannot find enqueued record for key: 0|com.ojm.pinstream|576|null|10191
    05-06 12:41:29.108  1808 12826 I NuCachedSource2: caching reached eos.
    05-06 12:41:29.108  1903  1966 E NotificationService: Suppressing notification from package by user request.
    05-06 12:41:29.117  1903  3205 E NotificationService: Suppressing notification from package by user request.
    05-06 12:41:29.117  1808 12818 D NuPlayerDriver: notifyResetComplete(0xe8513800)
    05-06 12:41:29.121  1903  1966 E NotificationService: Suppressing notification from package by user request.
    05-06 12:41:29.123  2663  2663 W StatusBar: removeNotification for unknown key: 0|com.ojm.pinstream|576|null|10191
    

    我对Android开发没有太多经验。如果有人能提供任何帮助,我将不胜感激。

    1 回复  |  直到 7 年前
        1
  •  3
  •   Benjamin Scholtz Sampath Kumar    7 年前

    下面是在代码中调用的方法链:

    1. 音频启动
    2. startForeground(NOTIFICATION_ID, buildNotification()) 方法被调用
    3. buildNotification() 方法通过以下方法添加CMD\u PAUSE操作 getActionIntent(CMD_PAUSE)
    4. getActionIntent() 方法调用该方法 PendingIntent.getService(getApplicationContext(), 0, s, 0)

    问题是,您通过一种直接启动服务的方法获得PendingIntent- PendingIntent.getService() ,根据文件:

    检索将启动服务的挂起内容,如调用 {@link Context#startService Context.startService()}。开始 提供给服务的参数将来自额外的意图。

    当音频开始播放时,它会创建通知,获取CMD\u PAUSE操作的挂起意图,此挂起意图启动服务 handleIntent() 方法通过挂起的意图调用操作集,然后暂停音频。。。

    根据个人经验,您应该使用以下方法进行调查:

    MediaButtonReceiver.buildMediaButtonPendingIntent(context,
                                PlaybackStateCompat.ACTION_PLAY)
    

    请参见 MediaButtonReceiver documentation 了解更多详细信息。

    当您的媒体服务发生媒体事件(如按下暂停按钮)时,将调用onStartCommand(),因此您应该实现一个简单的回调来处理此意图:

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        MediaButtonReceiver.handleIntent(mSession, intent)
        return super.onStartCommand(intent, flags, startId)
    }
    

    您需要找到一种将新URI传递给服务的不同方式,例如使用MediaBrowser,或者更简单的方式,绑定到服务并调用一个方法从活动中刷新URI。您不应该从触发onStartCommand()的活动中调用startService()。