blob: 35594c4e1b00ee09da5a3a6df03bed55927b1b80 [file]
// Copyright 2026 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use bitflags::bitflags;
use bt_common::packet_encoding::{Decodable, Encodable, Error as PacketError};
use bt_common::{decodable_enum, Uuid};
use std::time::Duration;
/// 16-bit UUID for the Media Control Service (MCS).
/// Defined in MCS v1.0.1 Section 2
pub(crate) const MEDIA_CONTROL_SERVICE_UUID: Uuid = Uuid::from_u16(0x1848);
/// 16-bit UUID for the Generic Media Control Service (GMCS).
/// Defined in MCS v1.0.1 Section 2
pub(crate) const GENERIC_MEDIA_CONTROL_SERVICE_UUID: Uuid = Uuid::from_u16(0x1849);
// ============================================================================
// Characteristic UUIDs (Mandatory, defined in MCS v1.0.1 Section 3)
// ============================================================================
/// Media Player Name characteristic UUID.
pub(crate) const MEDIA_PLAYER_NAME_UUID: Uuid = Uuid::from_u16(0x2B93);
/// Media Player Icon URL characteristic UUID.
pub(crate) const MEDIA_PLAYER_ICON_URL_UUID: Uuid = Uuid::from_u16(0x2B95);
/// Track Changed characteristic UUID.
pub(crate) const TRACK_CHANGED_UUID: Uuid = Uuid::from_u16(0x2B96);
/// Track Title characteristic UUID.
pub(crate) const TRACK_TITLE_UUID: Uuid = Uuid::from_u16(0x2B97);
/// Track Duration characteristic UUID.
pub(crate) const TRACK_DURATION_UUID: Uuid = Uuid::from_u16(0x2B98);
/// Track Position characteristic UUID.
pub(crate) const TRACK_POSITION_UUID: Uuid = Uuid::from_u16(0x2B99);
/// Playback Speed characteristic UUID.
pub(crate) const PLAYBACK_SPEED_UUID: Uuid = Uuid::from_u16(0x2B9A);
/// Seeking Speed characteristic UUID.
pub(crate) const SEEKING_SPEED_UUID: Uuid = Uuid::from_u16(0x2B9B);
/// Playing Order characteristic UUID.
pub(crate) const PLAYING_ORDER_UUID: Uuid = Uuid::from_u16(0x2BA1);
/// Playing Orders Supported characteristic UUID.
pub(crate) const PLAYING_ORDERS_SUPPORTED_UUID: Uuid = Uuid::from_u16(0x2BA2);
/// Media State characteristic UUID.
pub(crate) const MEDIA_STATE_UUID: Uuid = Uuid::from_u16(0x2BA3);
/// Media Control Point characteristic UUID.
pub(crate) const MEDIA_CONTROL_POINT_UUID: Uuid = Uuid::from_u16(0x2BA4);
/// Media Control Point Opcodes Supported characteristic UUID.
pub(crate) const MEDIA_CONTROL_POINT_OPCODES_SUPPORTED_UUID: Uuid = Uuid::from_u16(0x2BA5);
/// Content Control ID (CCID) characteristic UUID.
pub(crate) const CONTENT_CONTROL_ID_UUID: Uuid = Uuid::from_u16(0x2BBA);
decodable_enum! {
/// State of the media player (MCS v1.0.1 Section 3.17, Table 3.5).
pub enum MediaState<u8, PacketError, OutOfRange> {
/// Media player is inactive.
Inactive = 0x00,
/// Media player is currently playing.
Playing = 0x01,
/// Media player is paused.
Paused = 0x02,
/// Media player is fast forwarding or rewinding.
Seeking = 0x03,
}
}
impl Encodable for MediaState {
type Error = PacketError;
fn encoded_len(&self) -> usize {
1
}
fn encode(&self, buf: &mut [u8]) -> Result<(), Self::Error> {
if buf.is_empty() {
return Err(PacketError::BufferTooSmall);
}
buf[0] = (*self).into();
Ok(())
}
}
impl Decodable for MediaState {
type Error = PacketError;
fn decode(buf: &[u8]) -> (Result<Self, Self::Error>, usize) {
if buf.is_empty() {
return (Err(PacketError::UnexpectedDataLength), 0);
}
(Self::try_from(buf[0]), 1)
}
}
decodable_enum! {
/// Result code returned in a Media Control Point notification (MCS v1.0.1 Section 3.18.2, Table 3.8).
pub enum ControlPointResultCode<u8, PacketError, OutOfRange> {
/// The procedure completed successfully.
Success = 0x01,
/// The opcode is not supported by the media player.
OpcodeNotSupported = 0x02,
/// The media player is inactive and cannot perform the operation.
MediaPlayerInactive = 0x03,
/// The command cannot be completed.
CommandCannotBeCompleted = 0x04,
}
}
impl Encodable for ControlPointResultCode {
type Error = PacketError;
fn encoded_len(&self) -> usize {
1
}
fn encode(&self, buf: &mut [u8]) -> Result<(), Self::Error> {
if buf.is_empty() {
return Err(PacketError::BufferTooSmall);
}
buf[0] = (*self).into();
Ok(())
}
}
impl Decodable for ControlPointResultCode {
type Error = PacketError;
fn decode(buf: &[u8]) -> (Result<Self, Self::Error>, usize) {
if buf.is_empty() {
return (Err(PacketError::UnexpectedDataLength), 0);
}
(Self::try_from(buf[0]), 1)
}
}
decodable_enum! {
/// Playing order mode for media playback (MCS v1.0.1 Section 3.15, Table 3.3).
pub enum PlayingOrder<u8, PacketError, OutOfRange> {
/// A single track is played once; there is no next track.
SingleOnce = 0x01,
/// A single track is played repeatedly; the next track is the current track.
SingleRepeat = 0x02,
/// The tracks within a group are played once in track order.
InOrderOnce = 0x03,
/// The tracks within a group are played in track order repeatedly.
InOrderRepeat = 0x04,
/// The tracks within a group are played once only from the oldest first.
OldestOnce = 0x05,
/// The tracks within a group are played from the oldest first repeatedly.
OldestRepeat = 0x06,
/// The tracks within a group are played once only from the newest first.
NewestOnce = 0x07,
/// The tracks within a group are played from the newest first repeatedly.
NewestRepeat = 0x08,
/// The tracks within a group are played in random order once.
ShuffleOnce = 0x09,
/// The tracks within a group are played in random order repeatedly.
ShuffleRepeat = 0x0A,
}
}
impl Encodable for PlayingOrder {
type Error = PacketError;
fn encoded_len(&self) -> usize {
1
}
fn encode(&self, buf: &mut [u8]) -> Result<(), Self::Error> {
if buf.is_empty() {
return Err(PacketError::BufferTooSmall);
}
buf[0] = (*self).into();
Ok(())
}
}
impl Decodable for PlayingOrder {
type Error = PacketError;
fn decode(buf: &[u8]) -> (Result<Self, Self::Error>, usize) {
if buf.is_empty() {
return (Err(PacketError::UnexpectedDataLength), 0);
}
(Self::try_from(buf[0]), 1)
}
}
impl Default for PlayingOrder {
/// Returns standard sequential playing order (`InOrderOnce`) by default.
fn default() -> Self {
Self::InOrderOnce
}
}
/// State of the playing order characteristics when enabled.
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct PlayingOrderState {
/// Currently selected playing order.
pub current: PlayingOrder,
/// Bitmask of supported playing orders.
pub supported: SupportedPlayingOrders,
}
impl PlayingOrderState {
/// Creates a new `PlayingOrderState` with the default current order
/// (`PlayingOrder::InOrderOnce`) and the given supported bitmask.
pub fn new(supported: SupportedPlayingOrders) -> Self {
Self { current: PlayingOrder::default(), supported }
}
}
bitflags! {
/// Bitmask representing supported playing orders (MCS v1.0.1 Section 3.16, Table 3.4).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SupportedPlayingOrders: u16 {
/// A single track is played once; there is no next track.
const SINGLE_ONCE = 0x0001;
/// A single track is played repeatedly; the next track is the current track.
const SINGLE_REPEAT = 0x0002;
/// The tracks within a group are played once in track order.
const IN_ORDER_ONCE = 0x0004;
/// The tracks within a group are played in track order repeatedly.
const IN_ORDER_REPEAT = 0x0008;
/// The tracks within a group are played once only from the oldest first.
const OLDEST_ONCE = 0x0010;
/// The tracks within a group are played from the oldest first repeatedly.
const OLDEST_REPEAT = 0x0020;
/// The tracks within a group are played once only from the newest first.
const NEWEST_ONCE = 0x0040;
/// The tracks within a group are played from the newest first repeatedly.
const NEWEST_REPEAT = 0x0080;
/// The tracks within a group are played in random order once.
const SHUFFLE_ONCE = 0x0100;
/// The tracks within a group are played in random order repeatedly.
const SHUFFLE_REPEAT = 0x0200;
}
}
impl Default for SupportedPlayingOrders {
/// Returns standard sequential playing order (`IN_ORDER_ONCE`) by default.
fn default() -> Self {
Self::IN_ORDER_ONCE
}
}
impl From<PlayingOrder> for SupportedPlayingOrders {
fn from(order: PlayingOrder) -> Self {
match order {
PlayingOrder::SingleOnce => Self::SINGLE_ONCE,
PlayingOrder::SingleRepeat => Self::SINGLE_REPEAT,
PlayingOrder::InOrderOnce => Self::IN_ORDER_ONCE,
PlayingOrder::InOrderRepeat => Self::IN_ORDER_REPEAT,
PlayingOrder::OldestOnce => Self::OLDEST_ONCE,
PlayingOrder::OldestRepeat => Self::OLDEST_REPEAT,
PlayingOrder::NewestOnce => Self::NEWEST_ONCE,
PlayingOrder::NewestRepeat => Self::NEWEST_REPEAT,
PlayingOrder::ShuffleOnce => Self::SHUFFLE_ONCE,
PlayingOrder::ShuffleRepeat => Self::SHUFFLE_REPEAT,
}
}
}
impl From<&PlayingOrder> for SupportedPlayingOrders {
fn from(order: &PlayingOrder) -> Self {
Self::from(*order)
}
}
impl Encodable for SupportedPlayingOrders {
type Error = PacketError;
fn encoded_len(&self) -> usize {
2
}
fn encode(&self, buf: &mut [u8]) -> Result<(), Self::Error> {
if buf.len() < 2 {
return Err(PacketError::BufferTooSmall);
}
buf[..2].copy_from_slice(&self.bits().to_le_bytes());
Ok(())
}
}
impl Decodable for SupportedPlayingOrders {
type Error = PacketError;
fn decode(buf: &[u8]) -> (Result<Self, Self::Error>, usize) {
if buf.len() < 2 {
return (Err(PacketError::UnexpectedDataLength), 0);
}
let raw = u16::from_le_bytes([buf[0], buf[1]]);
(Ok(Self::from_bits_truncate(raw)), 2)
}
}
/// Media Control Point Opcode (MCS v1.0.1 Section 3.18, Table 3.6).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MediaControlOpcode {
/// Start playing current track.
Play,
/// Pause playback.
Pause,
/// Fast rewind.
FastRewind,
/// Fast forward.
FastForward,
/// Stop playback.
Stop,
/// Move playback position relative to current position by offset.
MoveRelative(i32),
/// Jump to previous segment.
PreviousSegment,
/// Jump to next segment.
NextSegment,
/// Jump to first segment.
FirstSegment,
/// Jump to last segment.
LastSegment,
/// Jump to specified segment number.
GotoSegment(i32),
/// Jump to previous track.
PreviousTrack,
/// Jump to next track.
NextTrack,
/// Jump to first track.
FirstTrack,
/// Jump to last track.
LastTrack,
/// Jump to specified track number.
GotoTrack(i32),
/// Jump to previous group.
PreviousGroup,
/// Jump to next group.
NextGroup,
/// Jump to first group.
FirstGroup,
/// Jump to last group.
LastGroup,
/// Jump to specified group number.
GotoGroup(i32),
}
impl MediaControlOpcode {
/// Returns the raw 1-byte opcode value defined in MCS v1.0.1 Table 3.6.
pub fn raw_opcode(&self) -> u8 {
match self {
Self::Play => 0x01,
Self::Pause => 0x02,
Self::FastRewind => 0x03,
Self::FastForward => 0x04,
Self::Stop => 0x05,
Self::MoveRelative(_) => 0x10,
Self::PreviousSegment => 0x20,
Self::NextSegment => 0x21,
Self::FirstSegment => 0x22,
Self::LastSegment => 0x23,
Self::GotoSegment(_) => 0x24,
Self::PreviousTrack => 0x30,
Self::NextTrack => 0x31,
Self::FirstTrack => 0x32,
Self::LastTrack => 0x33,
Self::GotoTrack(_) => 0x34,
Self::PreviousGroup => 0x40,
Self::NextGroup => 0x41,
Self::FirstGroup => 0x42,
Self::LastGroup => 0x43,
Self::GotoGroup(_) => 0x44,
}
}
}
impl Encodable for MediaControlOpcode {
type Error = PacketError;
fn encoded_len(&self) -> usize {
match self {
Self::MoveRelative(_)
| Self::GotoSegment(_)
| Self::GotoTrack(_)
| Self::GotoGroup(_) => 5,
_ => 1,
}
}
fn encode(&self, buf: &mut [u8]) -> Result<(), Self::Error> {
if buf.len() < self.encoded_len() {
return Err(PacketError::BufferTooSmall);
}
buf[0] = self.raw_opcode();
match self {
Self::MoveRelative(param)
| Self::GotoSegment(param)
| Self::GotoTrack(param)
| Self::GotoGroup(param) => {
buf[1..5].copy_from_slice(&param.to_le_bytes());
}
_ => {}
}
Ok(())
}
}
impl Decodable for MediaControlOpcode {
type Error = PacketError;
fn decode(buf: &[u8]) -> (Result<Self, Self::Error>, usize) {
if buf.is_empty() {
return (Err(PacketError::UnexpectedDataLength), 0);
}
let opcode = buf[0];
match opcode {
0x01 => (Ok(Self::Play), 1),
0x02 => (Ok(Self::Pause), 1),
0x03 => (Ok(Self::FastRewind), 1),
0x04 => (Ok(Self::FastForward), 1),
0x05 => (Ok(Self::Stop), 1),
0x10 => {
if buf.len() < 5 {
(Err(PacketError::UnexpectedDataLength), buf.len())
} else {
let param = i32::from_le_bytes([buf[1], buf[2], buf[3], buf[4]]);
(Ok(Self::MoveRelative(param)), 5)
}
}
0x20 => (Ok(Self::PreviousSegment), 1),
0x21 => (Ok(Self::NextSegment), 1),
0x22 => (Ok(Self::FirstSegment), 1),
0x23 => (Ok(Self::LastSegment), 1),
0x24 => {
if buf.len() < 5 {
(Err(PacketError::UnexpectedDataLength), buf.len())
} else {
let param = i32::from_le_bytes([buf[1], buf[2], buf[3], buf[4]]);
(Ok(Self::GotoSegment(param)), 5)
}
}
0x30 => (Ok(Self::PreviousTrack), 1),
0x31 => (Ok(Self::NextTrack), 1),
0x32 => (Ok(Self::FirstTrack), 1),
0x33 => (Ok(Self::LastTrack), 1),
0x34 => {
if buf.len() < 5 {
(Err(PacketError::UnexpectedDataLength), buf.len())
} else {
let param = i32::from_le_bytes([buf[1], buf[2], buf[3], buf[4]]);
(Ok(Self::GotoTrack(param)), 5)
}
}
0x40 => (Ok(Self::PreviousGroup), 1),
0x41 => (Ok(Self::NextGroup), 1),
0x42 => (Ok(Self::FirstGroup), 1),
0x43 => (Ok(Self::LastGroup), 1),
0x44 => {
if buf.len() < 5 {
(Err(PacketError::UnexpectedDataLength), buf.len())
} else {
let param = i32::from_le_bytes([buf[1], buf[2], buf[3], buf[4]]);
(Ok(Self::GotoGroup(param)), 5)
}
}
_ => (Err(PacketError::OutOfRange), 1),
}
}
}
bitflags! {
/// Bitmask representing supported Media Control Point opcodes (MCS v1.0.1 Section 3.19, Table 3.9).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SupportedOpcodes: u32 {
const PLAY = 0x00000001;
const PAUSE = 0x00000002;
const FAST_REWIND = 0x00000004;
const FAST_FORWARD = 0x00000008;
const STOP = 0x00000010;
const MOVE_RELATIVE = 0x00000020;
const PREVIOUS_SEGMENT = 0x00000040;
const NEXT_SEGMENT = 0x00000080;
const FIRST_SEGMENT = 0x00000100;
const LAST_SEGMENT = 0x00000200;
const GOTO_SEGMENT = 0x00000400;
const PREVIOUS_TRACK = 0x00000800;
const NEXT_TRACK = 0x00001000;
const FIRST_TRACK = 0x00002000;
const LAST_TRACK = 0x00004000;
const GOTO_TRACK = 0x00008000;
const PREVIOUS_GROUP = 0x00010000;
const NEXT_GROUP = 0x00020000;
const FIRST_GROUP = 0x00040000;
const LAST_GROUP = 0x00080000;
const GOTO_GROUP = 0x00100000;
}
}
impl Default for SupportedOpcodes {
/// Returns basic media playback control opcodes (`PLAY | PAUSE | STOP`) by
/// default.
fn default() -> Self {
Self::PLAY | Self::PAUSE | Self::STOP
}
}
impl From<&MediaControlOpcode> for SupportedOpcodes {
fn from(opcode: &MediaControlOpcode) -> Self {
match opcode {
MediaControlOpcode::Play => Self::PLAY,
MediaControlOpcode::Pause => Self::PAUSE,
MediaControlOpcode::FastRewind => Self::FAST_REWIND,
MediaControlOpcode::FastForward => Self::FAST_FORWARD,
MediaControlOpcode::Stop => Self::STOP,
MediaControlOpcode::MoveRelative(_) => Self::MOVE_RELATIVE,
MediaControlOpcode::PreviousSegment => Self::PREVIOUS_SEGMENT,
MediaControlOpcode::NextSegment => Self::NEXT_SEGMENT,
MediaControlOpcode::FirstSegment => Self::FIRST_SEGMENT,
MediaControlOpcode::LastSegment => Self::LAST_SEGMENT,
MediaControlOpcode::GotoSegment(_) => Self::GOTO_SEGMENT,
MediaControlOpcode::PreviousTrack => Self::PREVIOUS_TRACK,
MediaControlOpcode::NextTrack => Self::NEXT_TRACK,
MediaControlOpcode::FirstTrack => Self::FIRST_TRACK,
MediaControlOpcode::LastTrack => Self::LAST_TRACK,
MediaControlOpcode::GotoTrack(_) => Self::GOTO_TRACK,
MediaControlOpcode::PreviousGroup => Self::PREVIOUS_GROUP,
MediaControlOpcode::NextGroup => Self::NEXT_GROUP,
MediaControlOpcode::FirstGroup => Self::FIRST_GROUP,
MediaControlOpcode::LastGroup => Self::LAST_GROUP,
MediaControlOpcode::GotoGroup(_) => Self::GOTO_GROUP,
}
}
}
impl From<MediaControlOpcode> for SupportedOpcodes {
fn from(opcode: MediaControlOpcode) -> Self {
Self::from(&opcode)
}
}
impl Encodable for SupportedOpcodes {
type Error = PacketError;
fn encoded_len(&self) -> usize {
4
}
fn encode(&self, buf: &mut [u8]) -> Result<(), Self::Error> {
if buf.len() < 4 {
return Err(PacketError::BufferTooSmall);
}
buf[..4].copy_from_slice(&self.bits().to_le_bytes());
Ok(())
}
}
impl Decodable for SupportedOpcodes {
type Error = PacketError;
fn decode(buf: &[u8]) -> (Result<Self, Self::Error>, usize) {
if buf.len() < 4 {
return (Err(PacketError::UnexpectedDataLength), 0);
}
let raw = u32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]]);
(Ok(Self::from_bits_truncate(raw)), 4)
}
}
/// Total duration of the current track (MCS v1.0.1 Section 3.6).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TrackDuration {
/// Total duration of the current track.
Duration(Duration),
/// Duration is unknown or no track is loaded.
Unknown,
}
impl TrackDuration {
/// Size of the encoded Track Duration characteristic in bytes.
pub const SIZE_BYTES: usize = 4;
/// Raw integer value representing unknown track duration.
pub const UNKNOWN_RAW: i32 = -1;
/// Creates a known track duration.
pub const fn from_duration(duration: Duration) -> Self {
Self::Duration(duration)
}
/// Creates a track duration from a raw 10-millisecond unit 32-bit signed
/// integer.
pub fn from_raw_10ms(raw: i32) -> Self {
match raw {
Self::UNKNOWN_RAW => Self::Unknown,
dur if dur >= 0 => Self::Duration(Duration::from_millis(dur as u64 * 10)),
_ => Self::Unknown,
}
}
/// Returns the raw 32-bit signed integer in 10-millisecond units.
pub fn raw_10ms(&self) -> i32 {
match self {
Self::Unknown => Self::UNKNOWN_RAW,
Self::Duration(d) => (d.as_millis() / 10).min(i32::MAX as u128) as i32,
}
}
}
impl Encodable for TrackDuration {
type Error = PacketError;
fn encoded_len(&self) -> usize {
Self::SIZE_BYTES
}
fn encode(&self, buf: &mut [u8]) -> Result<(), Self::Error> {
if buf.len() < Self::SIZE_BYTES {
return Err(PacketError::BufferTooSmall);
}
buf[0..Self::SIZE_BYTES].copy_from_slice(&self.raw_10ms().to_le_bytes());
Ok(())
}
}
impl Decodable for TrackDuration {
type Error = PacketError;
fn decode(buf: &[u8]) -> (Result<Self, Self::Error>, usize) {
if buf.len() < Self::SIZE_BYTES {
return (Err(PacketError::UnexpectedDataLength), 0);
}
let raw = i32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]]);
(Ok(Self::from_raw_10ms(raw)), Self::SIZE_BYTES)
}
}
/// Current track position of the media player in 0.01-second (10 ms) resolution
/// (MCS v1.0.1 Section 3.7).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TrackPosition {
/// Absolute offset from the start of the current track.
FromStart(Duration),
/// Offset relative to the end of the current track.
FromEnd(Duration),
/// Track position is unavailable, e.g. when no track is loaded or for live
/// streams.
Unavailable,
}
impl TrackPosition {
/// Size of the encoded Track Position characteristic in bytes.
pub const SIZE_BYTES: usize = 4;
/// Raw integer value representing unavailable position.
pub const UNAVAILABLE_RAW: i32 = -1;
/// Creates a track position representing an absolute offset from the start.
pub const fn from_start(duration: Duration) -> Self {
Self::FromStart(duration)
}
/// Creates a track position representing an offset relative to the end.
pub const fn from_end(duration: Duration) -> Self {
Self::FromEnd(duration)
}
/// Creates a track position from a raw 10-millisecond unit 32-bit signed
/// integer.
pub fn from_raw_10ms(raw: i32) -> Self {
match raw {
Self::UNAVAILABLE_RAW => Self::Unavailable,
pos if pos >= 0 => Self::FromStart(Duration::from_millis(pos as u64 * 10)),
neg => Self::FromEnd(Duration::from_millis((-neg) as u64 * 10)),
}
}
/// Returns the raw 32-bit signed integer in 10-millisecond units.
pub fn raw_10ms(&self) -> i32 {
match self {
Self::Unavailable => Self::UNAVAILABLE_RAW,
Self::FromStart(d) => (d.as_millis() / 10).min(i32::MAX as u128) as i32,
Self::FromEnd(d) => -((d.as_millis() / 10).min(i32::MAX as u128) as i32),
}
}
}
impl Encodable for TrackPosition {
type Error = PacketError;
fn encoded_len(&self) -> usize {
Self::SIZE_BYTES
}
fn encode(&self, buf: &mut [u8]) -> Result<(), Self::Error> {
if buf.len() < Self::SIZE_BYTES {
return Err(PacketError::BufferTooSmall);
}
buf[0..Self::SIZE_BYTES].copy_from_slice(&self.raw_10ms().to_le_bytes());
Ok(())
}
}
impl Decodable for TrackPosition {
type Error = PacketError;
fn decode(buf: &[u8]) -> (Result<Self, Self::Error>, usize) {
if buf.len() < Self::SIZE_BYTES {
return (Err(PacketError::UnexpectedDataLength), 0);
}
let raw = i32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]]);
(Ok(Self::from_raw_10ms(raw)), Self::SIZE_BYTES)
}
}
/// Playback speed of the media player (MCS v1.0.1 Section 3.8).
/// Expressed as a power of 2 with a scale factor of 64: multiplier = 2^(speed /
/// 64).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PlaybackSpeed(i8);
impl PlaybackSpeed {
/// Minimum supported playback speed.
pub const MIN: Self = Self(-128);
/// Quarter (0.25x) playback speed.
pub const QUARTER: Self = Self(-128);
/// Half (0.50x) playback speed.
pub const HALF: Self = Self(-64);
/// Normal (1.00x) playback speed.
pub const NORMAL: Self = Self(0);
/// Double (2.00x) playback speed.
pub const DOUBLE: Self = Self(64);
/// Maximum supported playback speed.
pub const MAX: Self = Self(127);
/// Calculates the floating-point speed multiplier (e.g., 1.0 for normal,
/// 0.5 for half speed).
pub fn as_multiplier(&self) -> f64 {
2.0f64.powf(self.0 as f64 / 64.0)
}
/// Constructs a `PlaybackSpeed` from a floating-point multiplier, clamping
/// to the supported range.
pub fn from_multiplier(multiplier: f64) -> Self {
if multiplier <= 0.0 {
return Self::MIN;
}
let p = (64.0 * multiplier.log2()).round();
let clamped = p.clamp(i8::MIN as f64, i8::MAX as f64) as i8;
Self(clamped)
}
}
impl Default for PlaybackSpeed {
fn default() -> Self {
Self::NORMAL
}
}
impl From<i8> for PlaybackSpeed {
fn from(raw: i8) -> Self {
Self(raw)
}
}
impl From<PlaybackSpeed> for i8 {
fn from(speed: PlaybackSpeed) -> Self {
speed.0
}
}
impl From<PlaybackSpeed> for u8 {
fn from(speed: PlaybackSpeed) -> Self {
speed.0 as u8
}
}
impl TryFrom<&[u8]> for PlaybackSpeed {
type Error = PacketError;
fn try_from(buf: &[u8]) -> Result<Self, Self::Error> {
if buf.len() != 1 {
return Err(PacketError::UnexpectedDataLength);
}
Ok(Self(buf[0] as i8))
}
}
impl Encodable for PlaybackSpeed {
type Error = PacketError;
fn encoded_len(&self) -> usize {
1
}
fn encode(&self, buf: &mut [u8]) -> Result<(), Self::Error> {
if buf.is_empty() {
return Err(PacketError::BufferTooSmall);
}
buf[0] = self.0 as u8;
Ok(())
}
}
impl Decodable for PlaybackSpeed {
type Error = PacketError;
fn decode(buf: &[u8]) -> (Result<Self, Self::Error>, usize) {
if buf.is_empty() {
return (Err(PacketError::UnexpectedDataLength), 0);
}
(Ok(Self(buf[0] as i8)), 1)
}
}
/// Seeking speed factor of the media player (MCS v1.0.1 Section 3.9).
/// A signed integer representing the multiplier factor during fast-forwarding
/// or fast-rewinding.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SeekingSpeed(i8);
impl SeekingSpeed {
/// Not seeking.
pub const NOT_SEEKING: Self = Self(0);
/// Constructs a `SeekingSpeed` from the signed speed factor.
pub const fn new(factor: i8) -> Self {
Self(factor)
}
/// Returns the signed seeking factor.
pub const fn factor(&self) -> i8 {
self.0
}
/// Returns `true` if the media player is currently fast-forwarding or
/// rewinding.
pub const fn is_seeking(&self) -> bool {
self.0 != 0
}
/// Returns `true` if the media player is fast-forwarding.
pub const fn is_fast_forwarding(&self) -> bool {
self.0 > 0
}
/// Returns `true` if the media player is fast-rewinding.
pub const fn is_fast_rewinding(&self) -> bool {
self.0 < 0
}
}
impl Default for SeekingSpeed {
fn default() -> Self {
Self::NOT_SEEKING
}
}
impl From<i8> for SeekingSpeed {
fn from(raw: i8) -> Self {
Self(raw)
}
}
impl From<SeekingSpeed> for i8 {
fn from(speed: SeekingSpeed) -> Self {
speed.0
}
}
impl From<SeekingSpeed> for u8 {
fn from(speed: SeekingSpeed) -> Self {
speed.0 as u8
}
}
impl TryFrom<&[u8]> for SeekingSpeed {
type Error = PacketError;
fn try_from(buf: &[u8]) -> Result<Self, Self::Error> {
if buf.len() != 1 {
return Err(PacketError::UnexpectedDataLength);
}
Ok(Self(buf[0] as i8))
}
}
impl Encodable for SeekingSpeed {
type Error = PacketError;
fn encoded_len(&self) -> usize {
1
}
fn encode(&self, buf: &mut [u8]) -> Result<(), Self::Error> {
if buf.is_empty() {
return Err(PacketError::BufferTooSmall);
}
buf[0] = self.0 as u8;
Ok(())
}
}
impl Decodable for SeekingSpeed {
type Error = PacketError;
fn decode(buf: &[u8]) -> (Result<Self, Self::Error>, usize) {
if buf.is_empty() {
return (Err(PacketError::UnexpectedDataLength), 0);
}
(Ok(Self(buf[0] as i8)), 1)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn media_state_encode_decode_success() {
let states = [
(MediaState::Inactive, 0x00),
(MediaState::Playing, 0x01),
(MediaState::Paused, 0x02),
(MediaState::Seeking, 0x03),
];
for (state, raw_byte) in states {
assert_eq!(u8::from(state), raw_byte);
assert_eq!(state.encoded_len(), 1);
let mut buf = [0u8; 1];
assert!(state.encode(&mut buf).is_ok());
assert_eq!(buf[0], raw_byte);
let (decoded, consumed) = MediaState::decode(&buf);
assert_eq!(consumed, 1);
assert_eq!(decoded.unwrap(), state);
}
}
#[test]
fn media_state_decode_error() {
// Test out of range
for &invalid in &[0x04, 0x05, 0xFF] {
let (err, consumed) = MediaState::decode(&[invalid]);
assert_eq!(consumed, 1);
assert_eq!(err.unwrap_err(), PacketError::OutOfRange);
}
// Test empty buffer
let (err, consumed) = MediaState::decode(&[]);
assert_eq!(consumed, 0);
assert_eq!(err.unwrap_err(), PacketError::UnexpectedDataLength);
// Test buffer too small for encode
let mut empty_buf = [0u8; 0];
assert_eq!(
MediaState::Playing.encode(&mut empty_buf).unwrap_err(),
PacketError::BufferTooSmall
);
}
#[test]
fn control_point_result_code_encode_decode_success() {
let codes = [
(ControlPointResultCode::Success, 0x01),
(ControlPointResultCode::OpcodeNotSupported, 0x02),
(ControlPointResultCode::MediaPlayerInactive, 0x03),
(ControlPointResultCode::CommandCannotBeCompleted, 0x04),
];
for (code, raw_byte) in codes {
assert_eq!(u8::from(code), raw_byte);
assert_eq!(code.encoded_len(), 1);
let mut buf = [0u8; 1];
assert!(code.encode(&mut buf).is_ok());
assert_eq!(buf[0], raw_byte);
let (decoded, consumed) = ControlPointResultCode::decode(&buf);
assert_eq!(consumed, 1);
assert_eq!(decoded.unwrap(), code);
}
}
#[test]
fn control_point_result_code_decode_error() {
// Test out of range
for &invalid in &[0x00, 0x05, 0x06, 0xFF] {
let (err, consumed) = ControlPointResultCode::decode(&[invalid]);
assert_eq!(consumed, 1);
assert_eq!(err.unwrap_err(), PacketError::OutOfRange);
}
// Test empty buffer
let (err, consumed) = ControlPointResultCode::decode(&[]);
assert_eq!(consumed, 0);
assert_eq!(err.unwrap_err(), PacketError::UnexpectedDataLength);
// Test buffer too small for encode
let mut empty_buf = [0u8; 0];
assert_eq!(
ControlPointResultCode::Success.encode(&mut empty_buf).unwrap_err(),
PacketError::BufferTooSmall
);
}
#[test]
fn playing_order_encode_decode_success() {
let orders = [
(PlayingOrder::SingleOnce, 0x01, SupportedPlayingOrders::SINGLE_ONCE),
(PlayingOrder::SingleRepeat, 0x02, SupportedPlayingOrders::SINGLE_REPEAT),
(PlayingOrder::InOrderOnce, 0x03, SupportedPlayingOrders::IN_ORDER_ONCE),
(PlayingOrder::InOrderRepeat, 0x04, SupportedPlayingOrders::IN_ORDER_REPEAT),
(PlayingOrder::OldestOnce, 0x05, SupportedPlayingOrders::OLDEST_ONCE),
(PlayingOrder::OldestRepeat, 0x06, SupportedPlayingOrders::OLDEST_REPEAT),
(PlayingOrder::NewestOnce, 0x07, SupportedPlayingOrders::NEWEST_ONCE),
(PlayingOrder::NewestRepeat, 0x08, SupportedPlayingOrders::NEWEST_REPEAT),
(PlayingOrder::ShuffleOnce, 0x09, SupportedPlayingOrders::SHUFFLE_ONCE),
(PlayingOrder::ShuffleRepeat, 0x0A, SupportedPlayingOrders::SHUFFLE_REPEAT),
];
for (order, raw_byte, flag) in orders {
assert_eq!(u8::from(order), raw_byte);
assert_eq!(order.encoded_len(), 1);
let mut buf = [0u8; 1];
assert!(order.encode(&mut buf).is_ok());
assert_eq!(buf[0], raw_byte);
let (decoded, consumed) = PlayingOrder::decode(&buf);
assert_eq!(consumed, 1);
assert_eq!(decoded.unwrap(), order);
let converted: SupportedPlayingOrders = order.into();
assert_eq!(converted, flag);
let converted_ref: SupportedPlayingOrders = (&order).into();
assert_eq!(converted_ref, flag);
assert!(SupportedPlayingOrders::all().contains(order.into()));
}
}
#[test]
fn playing_order_decode_error() {
// Test out of range
for &invalid in &[0x00, 0x0B, 0x10, 0xFF] {
let (err, consumed) = PlayingOrder::decode(&[invalid]);
assert_eq!(consumed, 1);
assert_eq!(err.unwrap_err(), PacketError::OutOfRange);
}
// Test empty buffer
let (err, consumed) = PlayingOrder::decode(&[]);
assert_eq!(consumed, 0);
assert_eq!(err.unwrap_err(), PacketError::UnexpectedDataLength);
// Test buffer too small for encode
let mut empty_buf = [0u8; 0];
assert_eq!(
PlayingOrder::InOrderOnce.encode(&mut empty_buf).unwrap_err(),
PacketError::BufferTooSmall
);
}
#[test]
fn supported_playing_orders_bitmask_success() {
assert_eq!(SupportedPlayingOrders::default(), SupportedPlayingOrders::IN_ORDER_ONCE);
assert_eq!(SupportedPlayingOrders::all().bits(), 0x03FF);
let supported = SupportedPlayingOrders::SINGLE_ONCE
| SupportedPlayingOrders::IN_ORDER_ONCE
| SupportedPlayingOrders::OLDEST_ONCE
| SupportedPlayingOrders::NEWEST_REPEAT
| SupportedPlayingOrders::SHUFFLE_REPEAT;
assert!(supported.contains(PlayingOrder::SingleOnce.into()));
assert!(supported.contains(PlayingOrder::InOrderOnce.into()));
assert!(supported.contains(PlayingOrder::OldestOnce.into()));
assert!(supported.contains(PlayingOrder::NewestRepeat.into()));
assert!(supported.contains(PlayingOrder::ShuffleRepeat.into()));
assert!(!supported.contains(PlayingOrder::SingleRepeat.into()));
assert!(!supported.contains(PlayingOrder::InOrderRepeat.into()));
assert!(!supported.contains(PlayingOrder::OldestRepeat.into()));
assert!(!supported.contains(PlayingOrder::NewestOnce.into()));
assert!(!supported.contains(PlayingOrder::ShuffleOnce.into()));
// Encode and decode SupportedPlayingOrders
let mut buf = [0u8; 2];
assert!(supported.encode(&mut buf).is_ok());
let (decoded, consumed) = SupportedPlayingOrders::decode(&buf);
assert_eq!(consumed, 2);
assert_eq!(decoded.unwrap(), supported);
}
#[test]
fn supported_playing_orders_error() {
let supported = SupportedPlayingOrders::SINGLE_ONCE;
// Buffer too small for SupportedPlayingOrders encode/decode
assert_eq!(supported.encode(&mut [0u8; 1]).unwrap_err(), PacketError::BufferTooSmall);
let (err, consumed) = SupportedPlayingOrders::decode(&[0x01]);
assert_eq!(consumed, 0);
assert_eq!(err.unwrap_err(), PacketError::UnexpectedDataLength);
}
#[test]
fn playing_order_state_defaults() {
let default_state = PlayingOrderState::default();
assert_eq!(default_state.current, PlayingOrder::InOrderOnce);
assert_eq!(default_state.supported, SupportedPlayingOrders::IN_ORDER_ONCE);
let custom_state = PlayingOrderState::new(SupportedPlayingOrders::all());
assert_eq!(custom_state.current, PlayingOrder::InOrderOnce);
assert_eq!(custom_state.supported, SupportedPlayingOrders::all());
}
#[test]
fn media_control_opcode_all_non_parameterized() {
let opcodes = [
(MediaControlOpcode::Play, 0x01),
(MediaControlOpcode::Pause, 0x02),
(MediaControlOpcode::FastRewind, 0x03),
(MediaControlOpcode::FastForward, 0x04),
(MediaControlOpcode::Stop, 0x05),
(MediaControlOpcode::PreviousSegment, 0x20),
(MediaControlOpcode::NextSegment, 0x21),
(MediaControlOpcode::FirstSegment, 0x22),
(MediaControlOpcode::LastSegment, 0x23),
(MediaControlOpcode::PreviousTrack, 0x30),
(MediaControlOpcode::NextTrack, 0x31),
(MediaControlOpcode::FirstTrack, 0x32),
(MediaControlOpcode::LastTrack, 0x33),
(MediaControlOpcode::PreviousGroup, 0x40),
(MediaControlOpcode::NextGroup, 0x41),
(MediaControlOpcode::FirstGroup, 0x42),
(MediaControlOpcode::LastGroup, 0x43),
];
for (opcode, raw_byte) in opcodes {
assert_eq!(opcode.raw_opcode(), raw_byte);
assert_eq!(opcode.encoded_len(), 1);
let mut buf = [0u8; 1];
assert!(opcode.encode(&mut buf).is_ok());
assert_eq!(buf[0], raw_byte);
let (decoded, consumed) = MediaControlOpcode::decode(&buf);
assert_eq!(consumed, 1);
assert_eq!(decoded.unwrap(), opcode);
// Buffer too small for encode
let mut empty = [0u8; 0];
assert_eq!(opcode.encode(&mut empty).unwrap_err(), PacketError::BufferTooSmall);
}
}
#[test]
fn media_control_opcode_parameterized() {
let test_cases = [
(MediaControlOpcode::MoveRelative(10), 0x10, 10i32),
(MediaControlOpcode::MoveRelative(-15), 0x10, -15i32),
(MediaControlOpcode::MoveRelative(0), 0x10, 0i32),
(MediaControlOpcode::GotoSegment(3), 0x24, 3i32),
(MediaControlOpcode::GotoSegment(-1), 0x24, -1i32),
(MediaControlOpcode::GotoSegment(0), 0x24, 0i32),
(MediaControlOpcode::GotoTrack(42), 0x34, 42i32),
(MediaControlOpcode::GotoTrack(-5), 0x34, -5i32),
(MediaControlOpcode::GotoTrack(0), 0x34, 0i32),
(MediaControlOpcode::GotoGroup(1), 0x44, 1i32),
(MediaControlOpcode::GotoGroup(-2), 0x44, -2i32),
(MediaControlOpcode::GotoGroup(0), 0x44, 0i32),
];
for (opcode, raw_byte, param) in test_cases {
assert_eq!(opcode.raw_opcode(), raw_byte);
assert_eq!(opcode.encoded_len(), 5);
let mut buf = [0u8; 5];
assert!(opcode.encode(&mut buf).is_ok());
assert_eq!(buf[0], raw_byte);
assert_eq!(&buf[1..5], &param.to_le_bytes());
let (decoded, consumed) = MediaControlOpcode::decode(&buf);
assert_eq!(consumed, 5);
assert_eq!(decoded.unwrap(), opcode);
// Buffer too small for encode
let mut small = [0u8; 4];
assert_eq!(opcode.encode(&mut small).unwrap_err(), PacketError::BufferTooSmall);
}
}
#[test]
fn media_control_opcode_truncated_and_invalid_buffers() {
// Opcode 0x10 (MoveRelative) requires 5 bytes
for len in 1..5 {
let buf = vec![0x10, 0x01, 0x02, 0x03];
let (err, consumed) = MediaControlOpcode::decode(&buf[..len]);
assert_eq!(consumed, len);
assert_eq!(err.unwrap_err(), PacketError::UnexpectedDataLength);
}
// Opcode 0x24 (GotoSegment) requires 5 bytes
let (err, consumed) = MediaControlOpcode::decode(&[0x24, 0x01]);
assert_eq!(consumed, 2);
assert_eq!(err.unwrap_err(), PacketError::UnexpectedDataLength);
// Opcode 0x34 (GotoTrack) requires 5 bytes
let (err, consumed) = MediaControlOpcode::decode(&[0x34, 0x01, 0x02, 0x03]);
assert_eq!(consumed, 4);
assert_eq!(err.unwrap_err(), PacketError::UnexpectedDataLength);
// Opcode 0x44 (GotoGroup) requires 5 bytes
let (err, consumed) = MediaControlOpcode::decode(&[0x44]);
assert_eq!(consumed, 1);
assert_eq!(err.unwrap_err(), PacketError::UnexpectedDataLength);
// Invalid / RFU opcodes
for &invalid in &[0x00, 0x06, 0x0F, 0x11, 0x1F, 0x25, 0x2F, 0x35, 0x3F, 0x45, 0xFF] {
let (err, consumed) = MediaControlOpcode::decode(&[invalid]);
assert_eq!(consumed, 1);
assert_eq!(err.unwrap_err(), PacketError::OutOfRange);
}
// Empty buffer
let (err, consumed) = MediaControlOpcode::decode(&[]);
assert_eq!(consumed, 0);
assert_eq!(err.unwrap_err(), PacketError::UnexpectedDataLength);
}
#[test]
fn supported_opcodes_bitmask_success() {
assert_eq!(
SupportedOpcodes::default(),
SupportedOpcodes::PLAY | SupportedOpcodes::PAUSE | SupportedOpcodes::STOP
);
assert_eq!(SupportedOpcodes::all().bits(), 0x001F_FFFF);
let opcode_flags = [
(MediaControlOpcode::Play, SupportedOpcodes::PLAY, 0x00000001),
(MediaControlOpcode::Pause, SupportedOpcodes::PAUSE, 0x00000002),
(MediaControlOpcode::FastRewind, SupportedOpcodes::FAST_REWIND, 0x00000004),
(MediaControlOpcode::FastForward, SupportedOpcodes::FAST_FORWARD, 0x00000008),
(MediaControlOpcode::Stop, SupportedOpcodes::STOP, 0x00000010),
(MediaControlOpcode::MoveRelative(0), SupportedOpcodes::MOVE_RELATIVE, 0x00000020),
(MediaControlOpcode::PreviousSegment, SupportedOpcodes::PREVIOUS_SEGMENT, 0x00000040),
(MediaControlOpcode::NextSegment, SupportedOpcodes::NEXT_SEGMENT, 0x00000080),
(MediaControlOpcode::FirstSegment, SupportedOpcodes::FIRST_SEGMENT, 0x00000100),
(MediaControlOpcode::LastSegment, SupportedOpcodes::LAST_SEGMENT, 0x00000200),
(MediaControlOpcode::GotoSegment(0), SupportedOpcodes::GOTO_SEGMENT, 0x00000400),
(MediaControlOpcode::PreviousTrack, SupportedOpcodes::PREVIOUS_TRACK, 0x00000800),
(MediaControlOpcode::NextTrack, SupportedOpcodes::NEXT_TRACK, 0x00001000),
(MediaControlOpcode::FirstTrack, SupportedOpcodes::FIRST_TRACK, 0x00002000),
(MediaControlOpcode::LastTrack, SupportedOpcodes::LAST_TRACK, 0x00004000),
(MediaControlOpcode::GotoTrack(0), SupportedOpcodes::GOTO_TRACK, 0x00008000),
(MediaControlOpcode::PreviousGroup, SupportedOpcodes::PREVIOUS_GROUP, 0x00010000),
(MediaControlOpcode::NextGroup, SupportedOpcodes::NEXT_GROUP, 0x00020000),
(MediaControlOpcode::FirstGroup, SupportedOpcodes::FIRST_GROUP, 0x00040000),
(MediaControlOpcode::LastGroup, SupportedOpcodes::LAST_GROUP, 0x00080000),
(MediaControlOpcode::GotoGroup(0), SupportedOpcodes::GOTO_GROUP, 0x00100000),
];
for (opcode, flag, bits) in opcode_flags {
assert_eq!(flag.bits(), bits);
let converted: SupportedOpcodes = opcode.into();
assert_eq!(converted, flag);
let converted_ref: SupportedOpcodes = (&opcode).into();
assert_eq!(converted_ref, flag);
assert!(SupportedOpcodes::all().contains(flag));
}
let supported = SupportedOpcodes::PLAY
| SupportedOpcodes::PAUSE
| SupportedOpcodes::MOVE_RELATIVE
| SupportedOpcodes::NEXT_TRACK;
assert!(supported.contains(MediaControlOpcode::Play.into()));
assert!(supported.contains(MediaControlOpcode::Pause.into()));
assert!(supported.contains(MediaControlOpcode::MoveRelative(30).into()));
assert!(supported.contains(MediaControlOpcode::NextTrack.into()));
assert!(!supported.contains(MediaControlOpcode::PreviousTrack.into()));
assert!(!supported.contains(MediaControlOpcode::FastForward.into()));
assert!(!supported.contains(MediaControlOpcode::GotoTrack(1).into()));
let mut buf = [0u8; 4];
assert!(supported.encode(&mut buf).is_ok());
let (decoded, consumed) = SupportedOpcodes::decode(&buf);
assert_eq!(consumed, 4);
assert_eq!(decoded.unwrap(), supported);
}
#[test]
fn supported_opcodes_error() {
let supported = SupportedOpcodes::PLAY;
// Buffer too small for SupportedOpcodes encode/decode
assert_eq!(supported.encode(&mut [0u8; 3]).unwrap_err(), PacketError::BufferTooSmall);
let (err, consumed) = SupportedOpcodes::decode(&[0x01, 0x02, 0x03]);
assert_eq!(consumed, 0);
assert_eq!(err.unwrap_err(), PacketError::UnexpectedDataLength);
}
#[test]
fn track_duration_encode_decode() {
assert_eq!(TrackDuration::Unknown.raw_10ms(), -1);
assert_eq!(TrackDuration::from_raw_10ms(-1), TrackDuration::Unknown);
assert_eq!(TrackDuration::from_raw_10ms(-50), TrackDuration::Unknown);
let dur = TrackDuration::from_duration(Duration::from_secs(180));
assert_eq!(dur.raw_10ms(), 18000);
assert_eq!(TrackDuration::from_raw_10ms(18000), dur);
let mut buf = [0u8; 4];
assert!(dur.encode(&mut buf).is_ok());
assert_eq!(buf, 18000i32.to_le_bytes());
let (decoded, consumed) = TrackDuration::decode(&buf);
assert_eq!(consumed, 4);
assert_eq!(decoded.unwrap(), dur);
let mut small = [0u8; 3];
assert_eq!(dur.encode(&mut small).unwrap_err(), PacketError::BufferTooSmall);
let (err, consumed) = TrackDuration::decode(&small);
assert_eq!(consumed, 0);
assert_eq!(err.unwrap_err(), PacketError::UnexpectedDataLength);
}
#[test]
fn track_position_encode_decode() {
assert_eq!(TrackPosition::Unavailable.raw_10ms(), -1);
assert_eq!(TrackPosition::from_raw_10ms(-1), TrackPosition::Unavailable);
let start_pos = TrackPosition::from_start(Duration::from_millis(1500));
assert_eq!(start_pos.raw_10ms(), 150);
assert_eq!(TrackPosition::from_raw_10ms(150), start_pos);
let end_pos = TrackPosition::from_end(Duration::from_millis(2000));
assert_eq!(end_pos.raw_10ms(), -200);
assert_eq!(TrackPosition::from_raw_10ms(-200), end_pos);
let mut buf = [0u8; 4];
assert!(start_pos.encode(&mut buf).is_ok());
assert_eq!(buf, 150i32.to_le_bytes());
let (decoded, consumed) = TrackPosition::decode(&buf);
assert_eq!(consumed, 4);
assert_eq!(decoded.unwrap(), start_pos);
let mut small = [0u8; 3];
assert_eq!(start_pos.encode(&mut small).unwrap_err(), PacketError::BufferTooSmall);
let (err, consumed) = TrackPosition::decode(&small);
assert_eq!(consumed, 0);
assert_eq!(err.unwrap_err(), PacketError::UnexpectedDataLength);
}
#[test]
fn playback_speed_helpers_and_encoding() {
assert_eq!(i8::from(PlaybackSpeed::NORMAL), 0);
assert_eq!(i8::from(PlaybackSpeed::QUARTER), -128);
assert_eq!(i8::from(PlaybackSpeed::HALF), -64);
assert_eq!(i8::from(PlaybackSpeed::DOUBLE), 64);
assert_eq!(i8::from(PlaybackSpeed::MAX), 127);
assert!((PlaybackSpeed::NORMAL.as_multiplier() - 1.0).abs() < 1e-6);
assert!((PlaybackSpeed::QUARTER.as_multiplier() - 0.25).abs() < 1e-6);
assert!((PlaybackSpeed::HALF.as_multiplier() - 0.5).abs() < 1e-6);
assert!((PlaybackSpeed::DOUBLE.as_multiplier() - 2.0).abs() < 1e-6);
assert_eq!(PlaybackSpeed::from_multiplier(1.0), PlaybackSpeed::NORMAL);
assert_eq!(PlaybackSpeed::from_multiplier(0.25), PlaybackSpeed::QUARTER);
assert_eq!(PlaybackSpeed::from_multiplier(0.5), PlaybackSpeed::HALF);
assert_eq!(PlaybackSpeed::from_multiplier(2.0), PlaybackSpeed::DOUBLE);
assert_eq!(PlaybackSpeed::from_multiplier(0.0), PlaybackSpeed::MIN);
assert_eq!(PlaybackSpeed::from_multiplier(100.0), PlaybackSpeed::MAX);
let speed = PlaybackSpeed::DOUBLE;
let mut buf = [0u8; 1];
assert!(speed.encode(&mut buf).is_ok());
assert_eq!(buf[0], 64);
let (decoded, consumed) = PlaybackSpeed::decode(&buf);
assert_eq!(consumed, 1);
assert_eq!(decoded.unwrap(), speed);
assert_eq!(PlaybackSpeed::try_from(&buf[..]).unwrap(), speed);
assert_eq!(
PlaybackSpeed::try_from(&[][..]).unwrap_err(),
PacketError::UnexpectedDataLength
);
assert_eq!(
PlaybackSpeed::try_from(&[1, 2][..]).unwrap_err(),
PacketError::UnexpectedDataLength
);
let mut empty = [0u8; 0];
assert_eq!(speed.encode(&mut empty).unwrap_err(), PacketError::BufferTooSmall);
let (err, consumed) = PlaybackSpeed::decode(&[]);
assert_eq!(consumed, 0);
assert_eq!(err.unwrap_err(), PacketError::UnexpectedDataLength);
}
#[test]
fn seeking_speed_helpers_and_encoding() {
assert_eq!(SeekingSpeed::NOT_SEEKING.factor(), 0);
assert!(!SeekingSpeed::NOT_SEEKING.is_seeking());
assert!(!SeekingSpeed::NOT_SEEKING.is_fast_forwarding());
assert!(!SeekingSpeed::NOT_SEEKING.is_fast_rewinding());
let fast_forward = SeekingSpeed::new(4);
assert_eq!(fast_forward.factor(), 4);
assert!(fast_forward.is_seeking());
assert!(fast_forward.is_fast_forwarding());
assert!(!fast_forward.is_fast_rewinding());
let fast_rewind = SeekingSpeed::new(-4);
assert_eq!(fast_rewind.factor(), -4);
assert!(fast_rewind.is_seeking());
assert!(!fast_rewind.is_fast_forwarding());
assert!(fast_rewind.is_fast_rewinding());
let mut buf = [0u8; 1];
assert!(fast_forward.encode(&mut buf).is_ok());
assert_eq!(buf[0], 4);
let (decoded, consumed) = SeekingSpeed::decode(&buf);
assert_eq!(consumed, 1);
assert_eq!(decoded.unwrap(), fast_forward);
assert_eq!(SeekingSpeed::try_from(&buf[..]).unwrap(), fast_forward);
assert_eq!(SeekingSpeed::try_from(&[][..]).unwrap_err(), PacketError::UnexpectedDataLength);
assert_eq!(
SeekingSpeed::try_from(&[1, 2][..]).unwrap_err(),
PacketError::UnexpectedDataLength
);
let mut empty = [0u8; 0];
assert_eq!(fast_forward.encode(&mut empty).unwrap_err(), PacketError::BufferTooSmall);
let (err, consumed) = SeekingSpeed::decode(&[]);
assert_eq!(consumed, 0);
assert_eq!(err.unwrap_err(), PacketError::UnexpectedDataLength);
}
}