Request AirPlay consent on media setup and renew pending reconnects

This commit is contained in:
toorux committed 2026-10-10 00:39:22 +08:00
1 parent 05f172e467
commit c44fad4fc4
2 files changed
+49

No files matched your search

+27
View File
@@ -35,3 +35,30 @@ if 'framely_client_device' not in s:
}
'''+marker)
p.write_text(s)
# A GStreamer sink can survive successive audio sessions. Announce each valid
# media SETUP independently of its first decoded buffer.
s=p.read_text()
if 'framely_media_request' not in s:
marker=' unsigned char type;\n LOGI("ct=%d'
assert marker in s
s=s.replace(marker,''' // framely_media_request: a new SETUP needs fresh consent after teardown.
const char* directory=getenv("FRAMELY_CAST_DIR"),*id=getenv("FRAMELY_CAST_ID");
if(directory&&id)FrameSink(directory,id,"AirPlay").event("request");
unsigned char type;
LOGI("ct=%d''',1)
p.write_text(s)
# Mirroring may have no audio stream. Its size report arrives before decoding,
# so it must also request consent independently of the decoder's first frame.
s=p.read_text()
if 'framely_mirror_request' not in s:
marker='extern "C" void video_report_size(void *cls, float *width_source, float *height_source, float *width, float *height) {'
assert marker in s
s=s.replace(marker,marker+'''
// framely_mirror_request: notify even while the video decoder is starting.
const char* directory=getenv("FRAMELY_CAST_DIR"),*id=getenv("FRAMELY_CAST_ID");
if(directory&&id&&*width_source>0&&*width_source<=4096&&*height_source>0&&*height_source<=4096)
FrameSink(directory,id,"AirPlay").event("request",(unsigned)*width_source,(unsigned)*height_source);
''',1)
p.write_text(s)
+22
View File
@@ -216,6 +216,10 @@ impl Receivers {
self.sessions.insert(id.clone(), e.clone());
self.events
.push_back(json!({"kind":"cast.request","session":e}));
} else if kind=="request" && e["protocol"]=="AirPlay" && self.sessions[&id]["accepted"]!=true {
// SETUP can recur on an open control connection after the
// previous consent toast expired. Renew the pending request.
self.events.push_back(json!({"kind":"cast.request","session":self.sessions[&id]}));
} else if matches!(kind, "video" | "audio") {
let session = self.sessions.get_mut(&id).unwrap();
if kind == "video" {
@@ -500,6 +504,24 @@ fn stop_child(child: &mut Child) {
mod tests {
use super::*;
#[test]
fn airplay_setup_requests_consent_before_frames_and_after_reconnect() {
let root=tempfile::tempdir().unwrap();
let mut r=Receivers::new(root.path().to_owned(),root.path().join("bin"));
r.airplay_id="0123".into();
for _ in 0..2 {
r.event(json!({"event":"request","id":"0123","protocol":"AirPlay"}));
assert_eq!(r.events.pop_front().unwrap()["kind"],"cast.request");
assert!(r.window("0123").is_err());
r.event(json!({"event":"request","id":"0123","protocol":"AirPlay"}));
assert_eq!(r.events.pop_front().unwrap()["kind"],"cast.request");
r.control("0123","accept",json!({})).unwrap();
assert_eq!(r.window("0123").unwrap()["accepted"],true);
r.event(json!({"event":"ended","id":"0123","protocol":"AirPlay"}));
assert!(!root.path().join("0123.accept").exists());
r.events.clear();
}
}
#[test]
fn consent_blocks_playback_controls_and_rejects_stale_airplay_sessions() {
let root = tempfile::tempdir().unwrap();
let mut r = Receivers::new(root.path().to_owned(), root.path().join("bin"));