C

Media Session Controller

Demonstrates media session API usage.

"View with one active media session"

Building and deploying the example

See specific steps relating to building and deploying Qt for Android Automotive examples.

Overview

This example demonstrates the usage of Qt Media Session API to control media sessions on Android Automotive.

Once a media session is started, the app shows a list containing all currently active sessions. Each of the items in the list represents one session, and can be manipulated via the controls provided.

Including the API

We need to import two Android Automotive modules: Media to use the media session API, and Base to use AndroidAppsUtils API to check for notifications access.

import QtAndroidAutomotive.Media
import QtAndroidAutomotive.Base

Starting the listener service

In order to receive data about media sessions, the app is an enabled notificationlistener. This can be achieved by declaring the notificationlistener service provided by the media sessions API in our AndroidManifest.xml file:

        <service
            android:name="org.qtproject.qt.android.mediasession.QtMediaNotificationListener"
            android:enabled="true"
            android:exported="true"
            android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
            <intent-filter>
                <action android:name="android.service.notification.NotificationListenerService" />
            </intent-filter>
        </service>

By default, apps do not have access to notifications. Therefore, if notifications are not accessible, we need to guide the user to the Settings app so that they can grant access to them.

if (!hasNotificationAccess()) {
    notificationsDialog.open()
}

Listing media session controllers

Controller objects are used for controlling sessions. These controllers can be acquired from the MediaSessionManager::activeControllers property:

ListView {
    id: sessionsList

    spacing: 10
    model: MediaSessionManager.activeControllers
    clip: true

    anchors {
        left: parent.left
        top: parent.top
        right: parent.right
        bottom: parent.bottom
        margins: 10
    }

    delegate: MediaControllerItem {}
}

In this list, each controller is represented by a MediaControllerItem QML element.

Using the controller

Media title and artist

The example uses the QMediaMetaData returned by MediaSessionController::metaData property:

Text {
    id: titleText

    width: parent.width
    elide: Text.ElideRight
    color: "#f3f3f4"
    text: root.modelData.metaData.stringValue(MediaMetaData.Title)
}

Text {
    id: artistText

    color: "#cecfd5"
    text: root.modelData.metaData.stringValue(MediaMetaData.AlbumArtist)
}
Controlling the media session

In order to control the media, we have some buttons that use the MediaSessionController::pause, MediaSessionController::play, MediaSessionController::skipToNext, and MediaSessionController::skipToPrevious methods.

Whether or not these controls are available is defined separately by each session. Availability of control functionalities can be detected by using the MediaSessionController::availableActions property.

MediaControlButton {
    id: prevButton

    onClicked: root.modelData.skipToPrevious()

    visible: root.modelData.availableActions &
             MediaSessionController.SkipToPreviousAction
    width: buttonRow.buttonSize
    height: buttonRow.buttonSize
    icon.source: "res/previous.png"
}

MediaControlButton {
    id: playButton

    onClicked: {
        if (playing)
            root.modelData.pause()
        else
            root.modelData.play()
    }

    readonly property bool playing: root.modelData.playbackState ===
                                    MediaSessionController.PlayingState
    visible: root.modelData.availableActions &
             (playing ?
                  MediaSessionController.PauseAction :
                  MediaSessionController.PlayAction)
    width: buttonRow.buttonSize
    height: buttonRow.buttonSize
    icon.source: {
        if (root.modelData.playbackState === MediaSessionController.PlayingState) {
            return "res/pause.png";
        } else {
            return "res/play.png";
        }
    }
}

MediaControlButton {
    id: nextButton

    onClicked: root.modelData.skipToNext()

    visible: root.modelData.availableActions &
             MediaSessionController.SkipToNextAction
    width: buttonRow.buttonSize
    height: buttonRow.buttonSize
    icon.source: "res/next.png"
}

MediaControlButton {
    id: stopButton

    onClicked: root.modelData.stop()

    visible: root.modelData.availableActions & MediaSessionController.StopAction
    width: buttonRow.buttonSize
    height: buttonRow.buttonSize
    icon.source: "res/stop.png"
}
Seeking to a position inside the media

For seeking through the media, the example provides a Slider that uses the MediaSessionController::position property to read and control the current playback position and the MediaSessionController::duration property to read the length of the media:

Slider {
    id: progressSlider

    onMoved: {
        // Position is set in milliseconds:
        root.modelData.position = value * 1000
    }

    width: parent.width
    from: 0
    // Duration and position are reported in milliseconds, converting to seconds
    to: (root.modelData.duration / 1000)
    value: (root.modelData.position / 1000)
    enabled: root.modelData.availableActions & MediaSessionController.PositionAction
}

To complete the display of playback duration and position, the example has two text elements. One to display the current position, the other to show the length of the media. These are read directly from the slider implemented above and formatted to be displayed in minutes and seconds:

Text {
    id: progressText

    readonly property string minutes:
        "%1".arg(parseInt(progressSlider.value / 60)).padStart(2, '0')

    readonly property string seconds:
        "%1".arg(parseInt(progressSlider.value % 60)).padStart(2, '0')

    color: "#cecfd5"
    text: "%1:%2".arg(minutes).arg(seconds)
    verticalAlignment: Text.AlignVCenter

    anchors {
        left: parent.left
        leftMargin: rootColumn.spacing
        verticalCenter: parent.verticalCenter
    }
}

Text {
    id: durationText

    readonly property string minutes:
        "%1".arg(parseInt(progressSlider.to / 60)).padStart(2, '0')
    readonly property string seconds:
        "%1".arg(parseInt(progressSlider.to % 60)).padStart(2, '0')

    color: "#cecfd5"
    text: "%1:%2".arg(minutes).arg(seconds)
    verticalAlignment: Text.AlignVCenter

    anchors {
        right: parent.right
        rightMargin: rootColumn.spacing
        verticalCenter: parent.verticalCenter
    }
}

Available under certain Qt licenses.
Find out more.