sl4f_lib/media_session/
facade.rs

1// Copyright 2023 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::common_utils::common::macros::{fx_err_and_bail, with_line};
6use crate::media_session::types::PlayerStatusWrapper;
7use anyhow::Error;
8use fidl::endpoints::create_request_stream;
9use fidl_fuchsia_media_sessions2::{
10    self as sessions2, ActiveSessionMarker, ActiveSessionProxy, PlayerRegistration, PublisherMarker,
11};
12use fuchsia_async as fasync;
13use fuchsia_component::client::connect_to_protocol;
14use fuchsia_sync::RwLock;
15use futures::StreamExt;
16use log::info;
17use std::sync::Arc;
18
19#[derive(Debug)]
20pub struct MediaSessionFacade {
21    active_session_proxy: Option<ActiveSessionProxy>,
22    player_task: RwLock<Option<fasync::Task<()>>>,
23    requests_received: Arc<RwLock<Vec<String>>>,
24}
25
26impl MediaSessionFacade {
27    pub fn new() -> Self {
28        Self {
29            active_session_proxy: None,
30            player_task: RwLock::new(None),
31            requests_received: Arc::new(RwLock::new(Vec::new())),
32        }
33    }
34
35    /// Returns the active session proxy.
36    fn get_active_session_proxy(&self) -> Result<ActiveSessionProxy, Error> {
37        if let Some(proxy) = &self.active_session_proxy {
38            Ok(proxy.clone())
39        } else {
40            match connect_to_protocol::<ActiveSessionMarker>() {
41                Ok(proxy) => Ok(proxy),
42                Err(e) => fx_err_and_bail!(
43                    &with_line!("MediaSessionFacade::get_active_session_proxy"),
44                    format_err!("Failed to create proxy: {:?}", e)
45                ),
46            }
47        }
48    }
49
50    /// Returns the active media session's player status.
51    /// If there's no active session, it will return a None type value.
52    pub async fn watch_active_session_status(&self) -> Result<Option<PlayerStatusWrapper>, Error> {
53        let active_session_proxy = &self
54            .get_active_session_proxy()
55            .map_err(|e| format_err!("Failed to get active session proxy: {}", e))?;
56        let session = active_session_proxy
57            .watch_active_session()
58            .await
59            .map_err(|e| format_err!("Failed to watch active session: {}", e))?;
60        if let Some(session_control_marker) = session {
61            let info_delta = session_control_marker
62                .into_proxy()
63                .watch_status()
64                .await
65                .map_err(|e| format_err!("Failed to watch session status: {}", e))?;
66            Ok(Some(info_delta.player_status.into()))
67        } else {
68            Ok(None)
69        }
70    }
71
72    /// Publishes a mock Media Player via the fuchsia.media.sessions2 Publisher protocol.
73    /// Clients can watch Media Sessions via the fuchsia.media.sessions2 Discovery protocol.
74    /// This mock player does not route requests; it just records the requests it receives.
75    /// The list of requests can be accessed through this facade's ListReceivedRequests command.
76    /// The Bluetooth AVRCP component forwards commands to the most recently activated player.
77    pub async fn publish_mock_player(&self) -> Result<(), Error> {
78        let requests_received = self.requests_received.clone();
79        let fut = async move {
80            let publisher = fuchsia_component::client::connect_to_protocol::<PublisherMarker>()
81                .expect("Failed to connect to Publisher");
82
83            let (player_client, mut player_request_stream) = create_request_stream();
84
85            publisher
86                .publish(
87                    player_client,
88                    &PlayerRegistration {
89                        domain: Some("domain://sl4f".to_string()),
90                        ..Default::default()
91                    },
92                )
93                .await
94                .expect("Failed to publish Player");
95
96            #[allow(clippy::collection_is_never_read)]
97            let mut _player_info_change_responder = None;
98            let mut sent_response = false;
99
100            loop {
101                match player_request_stream.next().await {
102                    Some(Ok(request)) => match request {
103                        sessions2::PlayerRequest::WatchInfoChange { responder } => {
104                            if !sent_response {
105                                let _ = responder.send(&sessions2::PlayerInfoDelta::default());
106                                sent_response = true;
107                            } else {
108                                // Save responder so request stream is not dropped
109                                _player_info_change_responder = Some(responder);
110                            }
111                        }
112                        request => {
113                            requests_received.write().push(request.method_name().to_string());
114                        }
115                    },
116                    Some(Err(e)) => {
117                        info!(e:?; "Mock player request stream error");
118                        break;
119                    }
120                    None => {
121                        info!("Mock player request stream terminated");
122                        break;
123                    }
124                }
125            }
126        };
127
128        if let Some(_task) = self.player_task.write().take() {
129            info!("Dropping old mock player");
130        }
131        *self.player_task.write() = Some(fasync::Task::spawn(fut));
132        info!("Publishing mock player");
133
134        Ok(())
135    }
136
137    /// Unpublishes a mock Media Player and resets the list of received requests.
138    /// Returns true if a mock Media Player was running and is now stopped.
139    pub async fn stop_mock_player(&self) -> Result<bool, Error> {
140        if let Some(_task) = self.player_task.write().take() {
141            info!("Stopping mock player");
142            self.requests_received.write().clear();
143            return Ok(true);
144        }
145        info!("Mock player stoppage requested, but no session exists");
146        Ok(false)
147    }
148
149    /// Returns a list of the player requests received by mock Media Players spawned by this
150    /// facade's PublishMockPlayer command.
151    pub async fn list_received_requests(&self) -> Result<Vec<String>, Error> {
152        Ok(self.requests_received.read().clone())
153    }
154}