MediaSession.kt

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package mozilla.components.concept.engine.mediasession

import android.graphics.Bitmap

/**
 * Value type that represents a media session that is present on the currently displayed page in a session.
 */
class MediaSession {

    /**
     * The representation of a media element's metadata.
     *
     * @property source The media URI.
     * @property duration The media duration in seconds.
     * @property width The video width in device pixels.
     * @property height The video height in device pixels.
     * @property audioTrackCount The audio track count.
     * @property videoTrackCount The video track count.
     */
    data class ElementMetadata(
        val source: String? = null,
        val duration: Double = -1.0,
        val width: Long = 0L,
        val height: Long = 0L,
        val audioTrackCount: Int = 0,
        val videoTrackCount: Int = 0,
    ) {
        val portrait: Boolean?
            get() = if (height == 0L && width == 0L) null else height > width
    }

    /**
     * The representation of a media session's metadata.
     *
     * @property title The media title string.
     * @property artist The media artist string.
     * @property album The media album string.
     * @property getArtwork Get the media artwork.
     */
    data class Metadata(
        val title: String? = null,
        val artist: String? = null,
        val album: String? = null,
        val getArtwork: (suspend () -> Bitmap?)?,
    )

    /**
     * Holds the details of the media session's playback state.
     *
     * @property duration The media duration in seconds.
     * @property position The current media playback position in seconds.
     * @property playbackRate The playback rate coefficient.
     */
    data class PositionState(
        val duration: Double = -1.0,
        val position: Double = 0.0,
        val playbackRate: Double = 0.0,
    )

    /**
     * Flags for supported media session features.
     *
     * Implementation note: This is a 1:1 mapping of the features that GeckoView notifies us about.
     * https://github.com/mozilla/gecko-dev/blob/master/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/MediaSession.java
     */
    data class Feature(val flags: Long = 0) {
        companion object {
            const val NONE: Long = 0
            const val PLAY: Long = 1L shl 0
            const val PAUSE: Long = 1L shl 1
            const val STOP: Long = 1L shl 2
            const val SEEK_TO: Long = 1L shl 3
            const val SEEK_FORWARD: Long = 1L shl 4
            const val SEEK_BACKWARD: Long = 1L shl 5
            const val SKIP_AD: Long = 1L shl 6
            const val NEXT_TRACK: Long = 1L shl 7
            const val PREVIOUS_TRACK: Long = 1L shl 8
            const val FOCUS: Long = 1L shl 9
        }

        /**
         * Returns `true` if this [Feature] contains the [type].
         */
        fun contains(flag: Long): Boolean = (flags and flag) != 0L

        /**
         * Returns `true` if this is [Feature] equal to the [other] [Feature].
         */
        override fun equals(other: Any?): Boolean {
            if (this === other) return true
            if (other !is Feature) return false
            if (flags != other.flags) return false
            return true
        }

        override fun hashCode() = flags.hashCode()
    }

    /**
     * A simplified media session playback state.
     */
    enum class PlaybackState {
        /**
         * Unknown. No state has been received from the engine yet.
         */
        UNKNOWN,

        /**
         * Playback of this [MediaSession] has stopped (either completed or aborted).
         */
        STOPPED,

        /**
         * This [MediaSession] is paused.
         */
        PAUSED,

        /**
         * This [MediaSession] is currently playing.
         */
        PLAYING,
    }

    /**
     * Controller for controlling playback of a media element.
     */
    interface Controller {
        /**
         * Pauses the media.
         */
        fun pause()

        /**
         * Stop playback for the media session.
         */
        fun stop()

        /**
         * Plays the media.
         */
        fun play()

        /**
         * Seek to a specific time.
         * Prefer using fast seeking when calling this in a sequence.
         * Don't use fast seeking for the last or only call in a sequence.
         *
         * @param time The time in seconds to move the playback time to.
         * @param fast Whether fast seeking should be used.
         */
        fun seekTo(time: Double, fast: Boolean)

        /**
         * Seek forward by a sensible number of seconds.
         */
        fun seekForward()

        /**
         * Seek backward by a sensible number of seconds.
         */
        fun seekBackward()

        /**
         * Select and play the next track.
         * Move playback to the next item in the playlist when supported.
         */
        fun nextTrack()

        /**
         * Select and play the previous track.
         * Move playback to the previous item in the playlist when supported.
         */
        fun previousTrack()

        /**
         * Skip the advertisement that is currently playing.
         */
        fun skipAd()

        /**
         * Set whether audio should be muted.
         * Muting audio is supported by default and does not require the media
         * session to be active.
         *
         * @param mute True if audio for this media session should be muted.
         */
        fun muteAudio(mute: Boolean)
    }
}