当前位置: 首页 > 知识库问答 >
问题:

如何创建具有交互式通知的Web无线电流的Android前台服务?

荀辰钊
2023-03-14

我正在尝试构建一个极其简单的广播流应用程序,它存储一个web广播URL列表,可以选择这些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个答案

颛孙玉石
2023-03-14

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

  1. 音频开始
  2. start Foreground(NOTIFICATION_ID,BuildNotify())方法被称为
  3. 方法通过getActionIntent(CMD_PAUSE)添加CMD_PAUSE操作
  4. getActionIntent()方法调用方法PendingIntent.getService(getApplication ationContext(),0, s,0)

问题是,您通过一个方法来获取您的PendingIntent,该方法直接启动服务-PendingIntent.getService(),根据留档:

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

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

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

MediaButtonReceiver.buildMediaButtonPendingIntent(context,
                            PlaybackStateCompat.ACTION_PLAY)

有关更多详细信息,请参阅媒体按钮接收器留档。

当媒体服务发生媒体事件(如按下暂停按钮)时,会调用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()。

 类似资料:
  • 所以,我有一个拥有50000用户的应用程序。最近我更新了我的android studio,把SDK编译成了27。我做了一些改变,以支持android 8的变化,包括通知的变化(例如,通知应该有唯一的渠道)。在Android One手机上测试了这个(运行android 8)。一切都很顺利。在更新playstore上的应用程序后,我在android 8设备上看到了这些崩溃(主要是谷歌Pixel手机)

  • 我正在研究为我们的客户报告工具创建PDF报告的不同方法。我找到了这个链接,他们可以在PDF文档上执行操作和表单。有可能以编程方式生成这些PDF吗?比如有没有API可以创建这样的PDF? 我使用过apache POI和iText,但它没有这些功能。有没有人遇到过任何开源或商业工具用于此目的? 我的主要目标用户将是iPad用户。因此在objective c中使用此API也会有所帮助。

  • 我创建了一个扩展服务并作为前台服务运行的类。我希望我的服务通知是持久的(即不通过刷卡删除)。但是,我的通知可以通过刷卡来撤销。 服务留档指出:...前台服务必须为状态栏提供通知,状态栏放在正在进行的标题下。这意味着除非服务停止或从前台删除,否则通知不能被驳回... 我确实设置了断点来检查是否命中了onDestroy()或stop Self(),但事实并非如此。该服务正在前台模式下运行,但我可以通过

  • 问题内容: 我希望能够发出通知,以提醒用户有关计时器已结束的信息,但是,当您单击通知时,我不希望有任何意图。 我已经尝试将null传递给意图 问题答案: 您可以传递参数 代替 上

  • 当我尝试使用Adobe LiveCycle ES4 Designer打开常规PDF(不包含XFA表单)时,它会提示一个窗口(新表单助手),并要求我选择如何使用PDF。选项包括: 创建具有固定页面的交互式表单 创建具有可流动布局的交互式表单 我选择第一个并继续。然后,我不做任何事情就保存文件(使用另存为)。另存为类型是“Acrobat(Static)PDF Form(. pdf)*”,事实上这是这种

  • 编辑 我的通知是这样创建的,到目前为止,它在数千个API<26的android设备上工作: