[JAVA] Comment créer un lecteur de musique Android imposant

Je vais faire quelque chose comme ça

sc1.png Créez un modèle pour un lecteur de musique très courant.   cap2.gif

De plus, nous prendrons une méthode qui peut prendre en charge Wear and Auto sans implémenter de traitement dédié.

Il n'y a pas d'articles en japonais sur les lecteurs de musique Android. J'ai fait un article parce que j'ai réussi à le mettre en œuvre avec divers efforts. Ce sera plus long, mais restez en contact si vous le souhaitez.

Si vous souhaitez voir uniquement l'implémentation, passez à l'implémentation.

À quoi devrait ressembler un lecteur de musique sur Android

Lorsque vous créez un lecteur de musique qui fonctionne sur un PC, utilisez diverses méthodes d'instances telles que MediaPlayer. Je pense que le principal moyen de le faire était de l'appeler en cliquant sur le bouton collé sur le formulaire.

fig1.png

Cependant, les plates-formes mobiles comme Android et iOS de ces dernières années ne se limitent pas aux smartphones. Il a été intégré à divers appareils tels que les montres, les audios de voiture et les téléviseurs. Ensuite, il est devenu nécessaire de se connecter à un smartphone, d'utiliser le lecteur à partir d'une montre intelligente ou d'un autoradio et d'afficher les informations sur la chanson. Si un développeur écrit du code pour divers appareils (pour Android Wear, Auto, TV, etc.), ce sera un lourd fardeau. Par conséquent, il y avait un mouvement pour standardiser ce traitement dans la série Android, et il a été implémenté dans Android 5.0. C'est "MediaSession".

Position et rôle de la session médiatique

fig2.png

La figure ci-dessus montre le mécanisme autour de la musique sur Android. Celles-ci sont similaires aux relations serveur-client.

La Media Session agit comme un serveur. Il fournit des fonctions pour faire fonctionner les chansons, l'état du lecteur, les informations sur les chansons, etc. aux clients connectés.

Le client demande une opération à partir de la session multimédia en utilisant Media Controller, Media Button Intent, etc. Affiche l'état du lecteur et les informations sur la chanson à notifier.

référence: Understanding MediaSession (Part 1/4) Understanding MediaSession (Part 2/4)

Intention du contrôleur multimédia et du bouton multimédia

Les deux sont utilisés pour demander à la Media Session de faire fonctionner le morceau (lecture, arrêt, saut, etc.). La différence est que «Media Button Intent» ne peut demander que des opérations, tandis que «Media Controller» peut également obtenir le statut du lecteur et des informations sur la chanson.

À propos, Media Session se voit attribuer un identifiant unique appelé «jeton de session». Si vous utilisez l'analogie du serveur ci-dessus, c'est comme une adresse IP. En d'autres termes, si vous connaissez le Token, vous pouvez vous connecter de l'extérieur. Lors de la connexion, passez Token au constructeur de Media Controller.

Exemple d'implémentation de session média

Je pense qu'il est difficile d'imaginer comment utiliser Media Session à partir de l'explication ci-dessus, j'ai donc fait un diagramme. fig3-3.png

En regardant la figure, il semble que vous faites quelque chose d'assez ennuyeux. Cependant, il est très logique de prendre la forme ci-dessus lors de la création d'un lecteur de musique sur la plate-forme Android.

Je pense que le lecteur de musique que vous utilisez habituellement continuera à jouer même si vous supprimez l'activité des tâches récentes. Ou même si vous ne le supprimez pas de la tâche, le passage à une autre application peut détruire l'activité lorsque vous manquez de mémoire. En d'autres termes, si vous laissez une partie du traitement lié à la lecture à l'activité, vous ne pourrez pas continuer la lecture lorsque l'activité est détruite.

Par conséquent, l'idée importante dans la création d'un lecteur de musique fonctionnant sur Android est d'implémenter toutes les fonctions pour lire des chansons du côté du service, et du côté de l'activité, d'effectuer des opérations telles que la lecture et la pause selon l'opération de l'utilisateur `` Session multimédia ''. C'est ** pour clarifier la division des rôles **, comme afficher simplement les informations de la chanson en cours de lecture depuis la Media Session sur l'interface utilisateur.

Vous n'avez pas à craindre que l'activité soit détruite en affichant simplement les informations de la chanson à l'utilisateur et en fournissant le contrôle pour contrôler le lecteur. Lorsque l'activité est régénérée, il vous suffit de vous reconnecter à la Media Session.

Référence: Présentation des applications multimédias

Session multimédia et navigateur multimédia

Dans l'exemple précédent, Media Session a été implémentée dans un service normal. Cependant, dans la pratique, il est recommandé de l'implémenter en utilisant le Media Browser Service, qui est une extension du Service.

La «Session multimédia» mentionnée précédemment agit comme un serveur qui fournit des opérations et des informations sur les chansons, et le «Service de navigateur multimédia» agit comme une bibliothèque musicale. Si vous l'implémentez conformément aux spécifications de Media Browser, vous pourrez utiliser des fonctions telles que la sélection automatique de chansons dans Wear et Auto. Il encapsule également des opérations telles que la liaison et l'obtention d'un jeton de session.

fig4.png Il semble qu'il n'y ait pas beaucoup de différence avec l'exemple précédent, car les noms des étapes ① et ② sont faciles à comprendre. En fait, une fonction pour fournir une liste de chansons sera ajoutée ici.

référence: Building a Media Browser Service Building a Media Browser Client

Parcourir la bibliothèque selon le concept de conception de Media Browser

Ensuite, il s'agit de parcourir la bibliothèque. En bref, c'est une fonction qui affiche une liste de chansons pouvant être jouées à l'utilisateur.

Je vous ai dit la chanson que vous souhaitez jouer dans ④ de l'exemple précédent, mais si vous n'avez pas de liste de chansons en premier lieu, vous ne pouvez pas faire de demande. Comme mentionné ci-dessus, Media Browser dispose d'un mécanisme pour fournir une bibliothèque musicale.

fig6.png

La figure ci-dessus montre le processus de connexion au Media Browser Service et l'obtention d'une liste de chansons. Appelez subscribe (MediaId) pour demander un élément multimédia du navigateur multimédia au service de navigateur multimédia. Ensuite, la liste des éléments multimédias est renvoyée. Les étapes ③ et ④ sont exécutées chaque fois que nécessaire.

Concept d'identification des médias

Dans Media Browser Service, tous les éléments des chansons, albums et genres sont représentés par la chaîne de caractères MediaId. Et il y a MediaBrowser.MediaItem dans la classe qui contient le MediaId et les données qui l'accompagnent.

Le contenu détenu par MediaItem a un indicateur qui identifie s'il a un élément enfant, un élément qui a le rôle d'un soi-disant dossier ou un élément de musique lisible qui n'a pas d'enfants. Voir la figure ci-dessous.

fig5.png

Orange est l'élément qui a l'indicateur de dossier, et bleu est l'élément qui a l'indicateur jouable. La chaîne écrite indique MediaId.

Ne vous méprenez pas ici, le MediaItem marqué par un dossier lui-même n'a pas réellement d'éléments enfants en tant qu'objets. Dans la figure ci-dessus, il a été écrit comme une structure arborescente, mais il n'y a pas de relation parent-enfant en termes d'objets.

Je pense que c'est difficile à comprendre, alors donnons un exemple concret. L'appel de Media Browser subscribe (mediaId) appellera ʻonLoadChildren () du Media Browser Serviceconnecté et retournera le contenu envoyé. Appelez initialementsubscribe (" root "). Ensuite, {" Genre "," Album "}sera retourné. Supposons qu'un utilisateur veuille être par genre. Puis appelezsubscribe (" Genre "). Puis {" Genre: Rock, "Genre: Jazz"} sera retourné. Allez ensuite dans subscribe (" Genre: Rock ")et vous obtiendrez{" music1 "," music2 "}`. Lorsque l'utilisateur sélectionne music1, le contrôleur multimédia demande que le morceau avec MediaId «music1» soit lu.

Je l'ai écrit comme si la musique1 et la musique2 étaient automatiquement renvoyées lorsque j'ai appelé subscribe (" Genre: Rock "), mais j'ai dû écrire le mécanisme moi-même. Par exemple, si "Genre:" est ajouté au début de MediaId, il est considéré comme un identifiant qui répertorie toutes les chansons d'un genre spécifique et la chanson est renvoyée. En d'autres termes, il est nécessaire de comprendre ce qui est requis uniquement par l'ID de média et de renvoyer la chanson ou la sous-catégorie.

Cependant, dans cet extrait, je donne simplement l'impression que deux chansons sont suspendues à la racine.

Liste des classes qui stockent des informations sur les chansons

Les cours préparés à l'avance sont les suivants.

Nom La description
MediaDescription Une classe qui contient un minimum de métadonnées.
MediaMetadata LesinformationssurleschansonsfourniesparMediaSessionsontdansceformat.Enplusdesmétadonnéesminimales,vouspouvezdéfinirdesdonnéespersonnaliséescommes'ils'agissaitd'untableauassociatif.getDescription()Vous pouvez également générer une description de média avec.
MediaBrowser.MediaItem Le format pour récupérer les informations de MediaBrowserService. En plus des chansons, des éléments avec des éléments enfants tels que des genres et des albums peuvent également être exprimés. Il contient une description du média.
MediaSession.QueueItem Il contient une description de média et est utilisé pour obtenir des informations sur les chansons de la file d'attente. Par conséquent, les informations d'index sont jointes.

Pourquoi vous pouvez travailler avec Wear and Auto

Vous pouvez voir que nous utilisons «Media Browser» et «Media Controller» pour nous connecter au «Media Browser Service» et à la «Media Session». En fait, Wear et Auto ont également ces deux. Dans les exemples jusqu'à présent, il était connecté depuis Activity, mais Wear et Auto sont simplement connectés de la même manière, il n'est donc pas nécessaire d'écrire un processus dédié.

Exemple de coopération

cap3.gif Les informations sur les morceaux livrés à partir de Media Session s'affichent. De plus, si vous définissez un élément de file d'attente dans Media Session, une liste de chansons s'affiche ci-dessous.

cap2.gif Le côté Wear a également MediaBrowser.subscribe, qui obtient une liste de chansons et les affiche. (Cette fois, seules les chansons, mais en fonction de l'implémentation de onLoadChildren, les menus par album ou par genre peuvent également être affichés)

Audio Focus Ce n'est pas un gros problème étant donné la présence de sessions multimédia et de navigateurs, mais c'est aussi une fonctionnalité importante. ʻAudio Focus est la version audio de Focus dans l'interface utilisateur. Assurez-vous qu'une seule application possède ʻAudio Focus. Et si seules les applications avec ʻAudio Focus` jouent des chansons, vous pouvez empêcher plusieurs applications de lire de la musique en même temps.

Cependant, le système Android ne vous avertit que lorsque la mise au point atteint ou échoue, vous écrivez donc votre propre traitement, par exemple une pause lorsque la mise au point est perdue (par exemple, lorsqu'une autre application commence à jouer). est nécessaire.

Il est temps de mettre en œuvre

Cette fois, nous utiliserons les données musicales stockées dans le dossier Assets. J'ai senti que ce serait compliqué quand l'histoire du stockage était impliquée, alors j'ai fait un compromis en pratiquant l'utilisation de Media Session. Les métadonnées de la chanson sont également codées en dur. Notez s'il vous plaît.

** Tout le code est ici **

Version cible

Étant donné que le canal de notification est ajouté dans Android 8.0 et qu'il sera nécessaire de créer une branche en fonction de la version, cette fois, la cible est fixée à 25 et le minimum à 21. (Nous n'acceptons pas Tsukkomi que ce n'est pas imadoki ^^;)

Bibliothèque utilisée

MediaPlayer fonctionne bien car il ne lit que les MP3, mais j'utilise ExoPlayer que j'utilise habituellement. ExoPlayer est un lecteur multimédia développé par Google et est conçu pour remplacer le MediaPlayer par défaut, qui peut avoir différentes implémentations en fonction de la version et du modèle. Au fur et à mesure de sa mise à jour, le nombre de formats pouvant être lus augmentera et il comportera également des fonctions telles que la diffusion en direct. Comment utiliser ExoPlayer Comment utiliser ExoPlayer Je pense que ce sera utile.

Il utilise également la bibliothèque de support. Je recommande également le fonctionnaire de Google car il absorbe les différences entre les versions. Par conséquent, utilisez celui avec Compat à la fin, tel que MediaSessionCompat. Les bibliothèques dépendantes sont les suivantes

build.gradle


dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:26.1.0'
    implementation 'com.android.support:design:26.1.0'
    implementation 'com.android.support.constraint:constraint-layout:1.0.2'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.1'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
    implementation 'com.google.android.exoplayer:exoplayer:2.6.1'
}

Construction

image.png

・ Maina c Chity ty Activité affichée · Bibliothèque musicale Je l'ai emprunté à l'échantillon officiel de Google. Les informations de la chanson sont codées en dur. ・ Service de musique Mise en œuvre du service Meida Browser.

Vous trouverez ci-dessous un diagramme de la relation entre les rappels et les méthodes. fig9.png

AndroidManifest.xml

AndroidManifest.xml


    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <service android:name=".MusicService">
            <intent-filter>
                <action android:name="android.media.browse.MediaBrowserService" />
            </intent-filter>
        </service>
        <receiver android:name="android.support.v4.media.session.MediaButtonReceiver">
            <intent-filter>
                <action android:name="android.intent.action.MEDIA_BUTTON"/>
            </intent-filter>
        </receiver>
    </application>

Ceci est un extrait de la partie Application. Enregistrez MusicService et configurez également le récepteur pour recevoir MediaButtonIntent.

MainActivity UI fig7.png   ** Cliquez ici pour le fichier xml de l'interface utilisateur (https://github.com/SIY1121/MediaSessionSample/blob/master/app/src/main/res/layout/activity_main.xml) **

Initialiser et se connecter

MainActivity.java


    MediaBrowserCompat mBrowser;
    MediaControllerCompat mController;

    //L'interface utilisateur est omise

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //Configuration de l'interface utilisateur (omis)

        //Gardez le service en marche
        //Cela n'est pas nécessaire si le service peut être arrêté en même temps que l'activité est détruite.
        startService(new Intent(this, MusicService.class));

        //Initialiser MediaBrowser
        mBrowser = new MediaBrowserCompat(this, new ComponentName(this, MusicService.class), connectionCallback, null);
        //Lien(Service de liaison)
        mBrowser.connect();
    }

    //Rappel appelé lors de la connexion
    private MediaBrowserCompat.ConnectionCallback connectionCallback = new MediaBrowserCompat.ConnectionCallback() {
        @Override
        public void onConnected() {
            try {
                //Depuis le jeton de session peut être obtenu lorsque la connexion est terminée
                //Créer un contrôleur multimédia en l'utilisant
                mController = new MediaControllerCompat(MainActivity.this, mBrowser.getSessionToken());
                //Définir un rappel lorsque l'état du lecteur ou les informations sur la chanson envoyées par le service changent
                mController.registerCallback(controllerCallback);

                //S'il est déjà en cours de lecture, appelez le rappel lui-même et mettez à jour l'interface utilisateur
                if (mController.getPlaybackState() != null && mController.getPlaybackState().getState() == PlaybackStateCompat.STATE_PLAYING) {
                    controllerCallback.onMetadataChanged(mController.getMetadata());
                    controllerCallback.onPlaybackStateChanged(mController.getPlaybackState());
                }


            } catch (RemoteException ex) {
                ex.printStackTrace();
                Toast.makeText(MainActivity.this, ex.getMessage(), Toast.LENGTH_LONG).show();
            }
            //Obtenez une liste de chansons jouables du service
            mBrowser.subscribe(mBrowser.getRoot(), subscriptionCallback);
        }
    };

    //Rappel qui est appelé lorsque vous vous abonnez
    private MediaBrowserCompat.SubscriptionCallback subscriptionCallback = new MediaBrowserCompat.SubscriptionCallback() {
        @Override
        public void onChildrenLoaded(@NonNull String parentId, @NonNull List<MediaBrowserCompat.MediaItem> children) {
            //Demande de lecture de la première chanson si elle n'est pas déjà en cours de lecture
            if (mController.getPlaybackState() == null)
                Play(children.get(0).getMediaId());
        }
    };

Commencez par démarrer Music Service.

Après cela, MediaBrowser est initialisé et connecté. Lorsque la connexion est terminée, ʻonConnected () est appelé, alors récupérez le Token avec mBrowser.getSessionToken () et passez-le au constructeur de MediaController` pour se connecter à la session média.

Appelez ensuite mBrowser.subscribe (mBrowser.getRoot (), subscriptionCallback) pour demander une liste de chansons. Lorsque le côté Service envoie une liste, le callback set ʻonChildrenLoaded () `est appelé, donc cette fois nous allons jouer le premier élément.

Communiquez avec Media Session en utilisant Media Controller

MainActivity.java


    private void Play(String id) {
        //Obtenez TransportControl pour demander l'opération de MediaController au service
        //Lorsque playFromMediaId est appelé, onPlayFromMediaId dans le rappel de MediaSession côté service est appelé.
        mController.getTransportControls().playFromMediaId(id, null);
    }

Vous pouvez obtenir le TransportControl utilisé pour demander une opération à la MediaSession avec mController.getTransportControls ().

Traitement lorsque les informations sont fournies par MediaSession

MainActivity.java


    //Rappel de MediaController
    private MediaControllerCompat.Callback controllerCallback = new MediaControllerCompat.Callback() {
        //Appelé lorsque les informations du morceau en cours de lecture sont modifiées
        @Override
        public void onMetadataChanged(MediaMetadataCompat metadata) {
            textView_title.setText(metadata.getDescription().getTitle());
            imageView.setImageBitmap(metadata.getDescription().getIconBitmap());
            textView_duration.setText(Long2TimeString(metadata.getLong(MediaMetadataCompat.METADATA_KEY_DURATION)));
            seekBar.setMax((int) metadata.getLong(MediaMetadataCompat.METADATA_KEY_DURATION));
        }

        //Appelé lorsque l'état du joueur change
        @Override
        public void onPlaybackStateChanged(PlaybackStateCompat state) {

            //Changer le comportement du bouton et l'icône en fonction de l'état du joueur
            if (state.getState() == PlaybackStateCompat.STATE_PLAYING) {
                button_play.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        mController.getTransportControls().pause();
                    }
                });
                button_play.setImageResource(R.drawable.exo_controls_pause);
            } else {
                button_play.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        mController.getTransportControls().play();
                    }
                });
                button_play.setImageResource(R.drawable.exo_controls_play);
            }

            textView_position.setText(Long2TimeString(state.getPosition()));
            seekBar.setProgress((int) state.getPosition());

        }
    };

Lorsque l'état du lecteur ou du morceau à lire change, des informations sont envoyées à partir de MediaSession et le rappel de MediaController est appelé. Reflétez le changement dans l'interface utilisateur.

MusicService Créez un service qui hérite de MediaBrowserService.

Traitement lors de la connexion depuis un client

MusicService.java


    //Appelé lors de la connexion à un client
    //Décidez si vous souhaitez vous connecter à partir du nom du package, etc.
    //Autorisation de connexion lorsqu'une chaîne de caractères arbitraire est renvoyée
    //Refuser la connexion avec null
    //Autoriser toutes les connexions cette fois
    @Override
    public BrowserRoot onGetRoot(@NonNull String clientPackageName,
                                 int clientUid,
                                 Bundle rootHints) {
        Log.d(TAG, "Connected from pkg:" + clientPackageName + " uid:" + clientUid);
        return new BrowserRoot(ROOT_ID, null);
    }

    //Appelé lorsque le côté client appelle s'abonner
    //Renvoie le contenu de la bibliothèque musicale
    //Également utilisé pour la liste des chansons affichées dans Wear and Auto
    //Par défaut, la chaîne retournée par onGetRoot est passée à parentMediaId
    //L'ID est également transmis lorsque vous sélectionnez un MediaItem qui a un élément enfant sur l'écran du navigateur.
    @Override
    public void onLoadChildren(
            @NonNull final String parentMediaId,
            @NonNull final Result<List<MediaBrowserCompat.MediaItem>> result) {

        if (parentMediaId.equals(ROOT_ID))
            //Envoyer une liste de chansons au client
            result.sendResult(MusicLibrary.getMediaItems());
        else//Cette fois ROOT_Non valide sauf ID
            result.sendResult(new ArrayList<MediaBrowserCompat.MediaItem>());
    }

ʻOnGetRoot () est appelé lors de la connexion et ʻonLoadChildren () est appelé lors de l'inscription. Puisque ʻonLoadChildren () est censé prendre du temps, cela prend la forme d'envoyer le résultat à l'objet résultat au lieu de le renvoyer avec return. Si vous pouvez le renvoyer immédiatement, utilisez result.sendResult. Si vous souhaitez le renvoyer ultérieurement de manière asynchrone, utilisez result.detatch ()`.

Initialisation

MusicService.java


    final String TAG = MusicService.class.getSimpleName();//Balise de journal
    final String ROOT_ID = "root";//ID onGetRoot pour retourner au client/Utilisé dans onLoadChildren

    Handler handler;//Manutentionnaire pour le tournage régulier

    MediaSessionCompat mSession;//Session médiatique du rôle principal
    AudioManager am;//Gestionnaire de gestion des AudioFoucs

    int index = 0;//Index en cours de lecture

    ExoPlayer exoPlayer;//La substance du lecteur de musique

    List<MediaSessionCompat.QueueItem> queueItems = new ArrayList<>();//Liste à utiliser pour la file d'attente

    @Override
    public void onCreate() {
        super.onCreate();
        //Obtenez AudioManager
        am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
        //Initialiser MediaSession
        mSession = new MediaSessionCompat(getApplicationContext(), TAG);
        //Définissez les fonctions fournies par cette session multimédia
        mSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | //Boutons de poignée tels que des écouteurs
                MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS | //Prend en charge l'utilisation des commandes de file d'attente
                MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS); //Fournit des commandes pour la lecture, l'arrêt, le saut, etc.

        //Définir le rappel en fonction de l'opération du client
        mSession.setCallback(callback);

        //Définir le jeton de session dans MediaBrowserService
        setSessionToken(mSession.getSessionToken());

        //Lorsque les métadonnées de la session multimédia et l'état du lecteur sont mis à jour
        //Créer une notification/Mise à jour
        mSession.getController().registerCallback(new MediaControllerCompat.Callback() {
            @Override
            public void onPlaybackStateChanged(PlaybackStateCompat state) {
                CreateNotification();
            }

            @Override
            public void onMetadataChanged(MediaMetadataCompat metadata) {
                CreateNotification();
            }
        });


        //Ajouter un élément à la file d'attente
        int i = 0;
        for (MediaBrowserCompat.MediaItem media : MusicLibrary.getMediaItems()) {
            queueItems.add(new MediaSessionCompat.QueueItem(media.getDescription(), i));
            i++;
        }
        mSession.setQueue(queueItems);//La file d'attente s'affiche dans Wear and Auto


        //Initialisation d'exoPlayer
        exoPlayer = ExoPlayerFactory.newSimpleInstance(getApplicationContext(), new DefaultTrackSelector());
        //Définir l'écouteur d'événement du joueur
        exoPlayer.addListener(eventListener);

        handler = new Handler();
        //Mettre à jour les informations de lecture toutes les 500 ms
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                //Mettre à jour pendant la lecture
                if (exoPlayer.getPlaybackState() == Player.STATE_READY && exoPlayer.getPlayWhenReady())
                    UpdatePlaybackState();

                //Courez à nouveau
                handler.postDelayed(this, 500);
            }
        }, 500);
    }

Comme le montant est important, je vais l'expliquer brièvement. Tout d'abord, à propos de mSession.setFlags () qui est fait après l'initialisation de MediaSession. Vous pouvez activer la fonction de session multimédia en définissant un indicateur. Veuillez noter qu'aucune fonction ne peut être utilisée si elle n'est pas réglée en sens inverse.

Puis setSessionToken (mSession.getSessionToken ()) Cela définit la valeur retournée par getSessionToken () sur le client (MediaBrowser).

De plus, étant donné qu'ExoPlayer sera utilisé cette fois, nous initialiserons ExoPlayer.

Enfin, le gestionnaire est utilisé pour appeler ʻUpdatePlaybackState () `à intervalles réguliers. Ce processus sera expliqué plus loin.

Gérer les demandes de sessions médias des clients

Il s'agit de la partie associée à Transport Control qui a été utilisée pour demander des opérations à MediaSession dans MainActivity. fig8.png

MusicService.java


    //Rappel pour la session média
    private MediaSessionCompat.Callback callback = new MediaSessionCompat.Callback() {

        //Lire à partir de l'identifiant de la chanson
        //Ceci est également appelé lorsqu'une chanson est sélectionnée à partir de l'écran de navigation Wear ou Auto.
        @Override
        public void onPlayFromMediaId(String mediaId, Bundle extras) {
            //Cette fois, lisez le fichier audio contenu dans le dossier Assets
            //Jouer depuis Uri
            DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(getApplicationContext(), Util.getUserAgent(getApplicationContext(), "AppName"));
            MediaSource mediaSource = new ExtractorMediaSource.Factory(dataSourceFactory).createMediaSource(Uri.parse("file:///android_asset/" + MusicLibrary.getMusicFilename(mediaId)));

            //Cette fois, l'index est facilement calculé à partir du mediaId.
            for (MediaSessionCompat.QueueItem item : queueItems)
                if (item.getDescription().getMediaId().equals(mediaId))
                    index = (int) item.getQueueId();

            exoPlayer.prepare(mediaSource);

            mSession.setActive(true);

            onPlay();

            //Définir les informations sur la chanson en cours de lecture, fournies par MediaSession
            mSession.setMetadata(MusicLibrary.getMetadata(getApplicationContext(), mediaId));
        }

        //Lorsqu'on lui a demandé de jouer
        @Override
        public void onPlay() {
            //Demander le focus audio
            if (am.requestAudioFocus(afChangeListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN) == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
                //Si vous pouvez l'obtenir, commencez à jouer
                mSession.setActive(true);
                exoPlayer.setPlayWhenReady(true);
            }
        }

        //Lorsqu'une pause est demandée
        @Override
        public void onPause() {
            exoPlayer.setPlayWhenReady(false);
            //Libérer le focus audio
            am.abandonAudioFocus(afChangeListener);
        }

        //Lorsqu'on lui a demandé d'arrêter
        @Override
        public void onStop() {
            onPause();
            mSession.setActive(false);
            //Libérer le focus audio
            am.abandonAudioFocus(afChangeListener);
        }

        //Lorsqu'une recherche est demandée
        @Override
        public void onSeekTo(long pos) {
            exoPlayer.seekTo(pos);
        }

        //Quand la chanson suivante est demandée
        @Override
        public void onSkipToNext() {
            index++;
            if (index >= MusicLibrary.getMediaItems().size())//Après avoir joué jusqu'à la fin de la bibliothèque
                index = 0;//Revenir au début

            onPlayFromMediaId(queueItems.get(index).getDescription().getMediaId(), null);
        }

        //Lorsque la chanson précédente est demandée
        @Override
        public void onSkipToPrevious() {
            index--;
            if (index < 0)//Lorsque l'indice passe en dessous de 0
                index = queueItems.size() - 1;//Passer à la dernière chanson

            onPlayFromMediaId(queueItems.get(index).getDescription().getMediaId(), null);
        }

        //Également appelé lorsqu'un élément de la file d'attente est sélectionné avec Wear ou Auto
        @Override
        public void onSkipToQueueItem(long i) {
            onPlayFromMediaId(queueItems.get((int)i).getDescription().getMediaId(), null);
        }

        //Appelé lorsque l'intention du bouton multimédia vole
        //Aucun remplacement requis (il suffit de cracher des journaux cette fois)
        //Les opérations qui peuvent être effectuées changent en fonction de l'indicateur Action de playbackState de MediaSession.
        @Override
        public boolean onMediaButtonEvent(Intent mediaButtonEvent) {
            KeyEvent key = mediaButtonEvent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
            Log.d(TAG, String.valueOf(key.getKeyCode()));
            return super.onMediaButtonEvent(mediaButtonEvent);
        }
    };

Il y a deux points à noter ici.

Tout d'abord, mSession.setMetadata () qui est le processus final de ʻonPlayFromMediaId () Si vous définissez les métadonnées pour la session multimédia, les informations sur la chanson seront transmises aux clients connectés. En prenant MainActivity comme exemple, le rappel MediaController ʻonMetadataChanged ()est appelé.

Puis ʻonMediaButtonEvent () ` Il s'agit de l'endroit qui sera appelé lorsque l'intention du bouton multimédia vole. Il comprend également des boutons physiques sur les écouteurs filaires et des commandes d'appareils connectés Bluetooth.

Contrairement aux autres méthodes, il existe déjà une implémentation ici, il n'est donc fondamentalement pas nécessaire de la remplacer. Par exemple, un traitement tel que le mappage d'une simple pression de bouton physique sur un casque sur onPlay () et onPause () et une double pression sur onSkipNext () a déjà été implémenté.

Cependant, si vous souhaitez ajouter votre propre opération en appuyant sur le bouton physique, vous la gérerez ici.

Informer le client du statut du joueur

MusicService.java


    //Rappel du joueur
    private Player.EventListener eventListener = new Player.DefaultEventListener() {
        //Appelé lorsque le statut du joueur change
        @Override
        public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
            UpdatePlaybackState();
        }
    };

    //Définir l'état actuel du lecteur fourni par MediaSession
    //Des informations sur la position de lecture sont également incluses ici, donc mettez-les régulièrement à jour.
    private void UpdatePlaybackState() {
        int state = PlaybackStateCompat.STATE_NONE;
        //Définissez le statut de session multimédia approprié à partir de l'état du lecteur
        switch (exoPlayer.getPlaybackState()) {
            case Player.STATE_IDLE:
                state = PlaybackStateCompat.STATE_NONE;
                break;
            case Player.STATE_BUFFERING:
                state = PlaybackStateCompat.STATE_BUFFERING;
                break;
            case Player.STATE_READY:
                if (exoPlayer.getPlayWhenReady())
                    state = PlaybackStateCompat.STATE_PLAYING;
                else
                    state = PlaybackStateCompat.STATE_PAUSED;
                break;
            case Player.STATE_ENDED:
                state = PlaybackStateCompat.STATE_STOPPED;
                break;
        }

        //Réglez les informations du lecteur, la position de lecture actuelle, etc.
        //Définissez également les opérations pouvant être effectuées avec MeidaButtonIntent.
        mSession.setPlaybackState(new PlaybackStateCompat.Builder()
                .setActions(PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PAUSE | PlaybackStateCompat.ACTION_SKIP_TO_NEXT | PlaybackStateCompat.ACTION_STOP)
                .setState(state, exoPlayer.getCurrentPosition(), exoPlayer.getPlaybackParameters().speed)
                .build());
    }

Le premier rappel provient d'ExoPlayer. Comme son nom l'indique, il est appelé lorsque le statut du joueur change.

ʻUpdatePlaybackState () `définit l'état du lecteur fourni par MediaSession. Le contenu à livrer est l'état tel que Lecture et Pause, la position de lecture, la vitesse de lecture et les opérations actuellement acceptées. Le statut PlaybackStateCompat.STATE_XXXX, qui indique l'état du lecteur, et le statut d'ExoPlayer sont différents et doivent être convertis en ceux correspondants. En passant, le fait que la position de lecture soit incluse signifie que la position de lecture doit toujours être fournie. Par conséquent, j'essaye d'appeler ici toutes les 0,5 secondes dans la partie d'initialisation (onCreate).

Créer une notification

MusicService.java


    //Créer une notification, rendre le service au premier plan
    private void CreateNotification() {
        MediaControllerCompat controller = mSession.getController();
        MediaMetadataCompat mediaMetadata = controller.getMetadata();

        if (mediaMetadata == null && !mSession.isActive()) return;

        MediaDescriptionCompat description = mediaMetadata.getDescription();

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

        builder
                //Définir les informations pour la chanson actuelle
                .setContentTitle(description.getTitle())
                .setContentText(description.getSubtitle())
                .setSubText(description.getDescription())
                .setLargeIcon(description.getIconBitmap())

                //Définir l'intention en cliquant sur la notification
                .setContentIntent(createContentIntent())

                //Définir l'intention lorsque les notifications sont supprimées
                .setDeleteIntent(MediaButtonReceiver.buildMediaButtonPendingIntent(this,
                        PlaybackStateCompat.ACTION_STOP))

                //Rendre la plage de notification publique afin qu'elle s'affiche sur l'écran de verrouillage
                .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)

                .setSmallIcon(R.drawable.exo_controls_play)
                //Définir la couleur utilisée pour la zone de notification
                //Les styles changent en fonction de la version d'Android et les couleurs ne sont souvent pas appliquées
                .setColor(ContextCompat.getColor(this, R.color.colorAccent))

                //Utiliser le style de média
                .setStyle(new android.support.v4.media.app.NotificationCompat.MediaStyle()
                        .setMediaSession(mSession.getSessionToken())
                        //Définit l'index du contrôle qui s'affiche lorsque la notification est repliée petit
                        .setShowActionsInCompactView(1));

        // Android4.Avant 4, vous ne pouvez pas faire glisser pour ignorer les notifications
        //Traitez en affichant le bouton d'annulation
        //Cette fois, le SDK minimum est de 21, donc ce n'est pas nécessaire
        //.setShowCancelButton(true)
        //.setCancelButtonIntent(MediaButtonReceiver.buildMediaButtonPendingIntent(this,
        //        PlaybackStateCompat.ACTION_STOP)));

        //Paramètres de contrôle des notifications
        builder.addAction(new NotificationCompat.Action(
                R.drawable.exo_controls_previous, "prev",
                MediaButtonReceiver.buildMediaButtonPendingIntent(this,
                        PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS)));

        //Définir les boutons de lecture et de pause dans l'état du lecteur
        if (controller.getPlaybackState().getState() == PlaybackStateCompat.STATE_PLAYING) {
            builder.addAction(new NotificationCompat.Action(
                    R.drawable.exo_controls_pause, "pause",
                    MediaButtonReceiver.buildMediaButtonPendingIntent(this,
                            PlaybackStateCompat.ACTION_PAUSE)));
        } else {
            builder.addAction(new NotificationCompat.Action(
                    R.drawable.exo_controls_play, "play",
                    MediaButtonReceiver.buildMediaButtonPendingIntent(this,
                            PlaybackStateCompat.ACTION_PLAY)));
        }


        builder.addAction(new NotificationCompat.Action(
                R.drawable.exo_controls_next, "next",
                MediaButtonReceiver.buildMediaButtonPendingIntent(this,
                        PlaybackStateCompat.ACTION_SKIP_TO_NEXT)));

        startForeground(1, builder.build());

        //Autoriser les balayages pour désactiver les notifications lorsque vous ne jouez pas
        if (controller.getPlaybackState().getState() != PlaybackStateCompat.STATE_PLAYING)
            stopForeground(false);
    }

Pour créer une notification avec contrôle de lecture, vous devez spécifier MediaStyle avec setStyle et définir l'intention du bouton multimédia avec addAction pour l'augmenter.

Gérer la mise au point audio

MusicService.java


    //Rappel de focus audio
    AudioManager.OnAudioFocusChangeListener afChangeListener =
            new AudioManager.OnAudioFocusChangeListener() {
                public void onAudioFocusChange(int focusChange) {
                    //Si vous perdez complètement votre concentration
                    if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
                        //Arrêtez
                        mSession.getController().getTransportControls().pause();
                    } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {//Mise au point temporaire perdue
                        //Arrêtez
                        mSession.getController().getTransportControls().pause();
                    } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {//Mise au point perdue en raison du son de notification (doit être désactivée et continuer à jouer)
                        //Normalement, vous devez baisser le volume temporairement, mais ne rien faire
                    } else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {//Quand la concentration est retrouvée
                        //Régénération
                        mSession.getController().getTransportControls().play();
                    }
                }
            };

Nous avons implémenté un mécanisme pour arrêter la lecture si une autre application commence à jouer et que la mise au point audio de cette application est perdue.

Impressions

J'ai trouvé que c'était très bien fait car il n'y avait qu'une API implémentée à partir d'Android 5.0.

Étant donné que de nombreux lecteurs de musique Android sont déjà sortis, je pense qu'il y a peu de possibilités de créer un lecteur de musique particulièrement pur par vous-même, mais cela peut être utile si vous avez la possibilité de jouer de la musique en arrière-plan, comme la radio Internet. Hmm.

J'ai essayé d'expliquer l'API Android Media dans un article. Cela fait longtemps et je suis moi-même surpris. Dans un tel cas, est-il plus facile de lire en le divisant?

Quoi qu'il en soit, merci d'avoir regardé jusqu'au bout.

Exemple officiel de Google

Exemple Android MediaBrowserService Cela a été simplifié et cet exemple a été créé. ・ Test du contrôleur multimédia Un échantillon qui implémente le navigateur multimédia et le contrôleur multimédia et peut contrôler d'autres applications musicales. ・ Exemple de lecteur de musique Android universel Une implémentation de lecteur de musique assez soignée. Vous utilisez correctement la fonction de navigation du service de navigateur multimédia.

Recommended Posts

Comment créer un lecteur de musique Android imposant
[Android] Comment créer un fragment de dialogue
Comment créer une application à l'aide de Tensorflow avec Android Studio
Comment faire un pot ombré
Comment créer un plug-in natif Unity (version Android)
Comment faire un générateur d'oléore en utilisant Swagger Codegen
Java - Comment créer JTable
Créez une application Android. (Jour 5)
Comment gérer les instances
[Rails] Comment faire des graines
[Version 2020] Comment envoyer un e-mail à l'aide d'Android Studio Javamail
Qu'est-ce qu'un objet immuable? [Explication comment faire]
Comment créer un conteneur Java
Comment créer un pilote JDBC
Créez une application Android. (Premier jour)
Comment insérer une bibliothèque externe
Comment créer un plug-in Jenkins
Comment faire un projet Maven
Comment créer un tableau Java
J'ai essayé de créer une application Android avec MVC maintenant (Java)
Comment recadrer une image avec libGDX
Créer un fichier exécutable avec Android Studio
Comment créer un résumé de calendrier Java
Comment brouiller l'image (super facile)
Je veux créer une application ios.android
[Android] Comment gérer les thèmes sombres
Comment détecter les conflits de microphone sur Android
Comment créer un robot Discord (Java)
Comment définir un bean de classe interne
Comment créer une application avec un mécanisme de plug-in [C # et Java]
Comment utiliser ExpandableListView dans Android Studio
Comment faire une méthode de jugement pour rechercher n'importe quel caractère dans le tableau
Je crée une application Android et elle est bloquée par des erreurs et comment la résoudre
[Android] Comment convertir une chaîne de caractères en resourceId
Comment écrire une instruction if pour améliorer la lisibilité-java
[Android] Comment détecter le changement de volume (= pression sur le bouton de volume)
Comment utiliser un tableau pour les clés HashMap
Comment réduire l'image de Spring Boot Docker
Comment jouer de la voix ou de la musique en utilisant javascript
Créer un logiciel qui reflète l'écran Android sur un PC 1
Comment rendre la logique de vérification en double plus lisible
Comment créer un JRE léger pour la distribution
Rails6.0 ~ Comment créer un environnement de développement respectueux de l'environnement
Comment résoudre les problèmes d'expression en Java
Comment écrire React Native Bridge ~ Version Android ~
[Rails] Comment créer un environnement avec Docker
Comment créer un certificat OLEORE (certificat SSL, auto-certificat)
[Java] Comment rendre plusieurs boucles for uniques
Comment installer Ruby sur une instance EC2 d'AWS
[Android Studio] Comment changer TextView en n'importe quelle police [Java]
[Swift] Comment lire des chansons de la bibliothèque musicale