use super::{
serialize_media, ClientAction, MediaChannel, ReceivedDataType, RtmpScheduler, ServerResult,
};
use crate::flv::flv_tag_body::{
is_audio_sequence_header, is_video_keyframe, is_video_sequence_header,
};
use bytes::Bytes;
use log::debug;
use rml_rtmp::time::RtmpTimestamp;
impl RtmpScheduler {
pub(super) fn handle_audio_video_data_received(
&mut self,
stream_key: &str,
timestamp: RtmpTimestamp,
data: Bytes,
data_type: ReceivedDataType,
server_results: &mut Vec<ServerResult>,
) {
let channel = match self.channels.get_mut(stream_key) {
Some(channel) => channel,
None => return,
};
let is_video = matches!(data_type, ReceivedDataType::Video);
let (is_keyframe, is_sequence_header) = if is_video {
(is_video_keyframe(&data), is_video_sequence_header(&data))
} else {
(false, is_audio_sequence_header(&data))
};
match data_type {
ReceivedDataType::Video => {
if is_sequence_header {
if channel
.video_sequence_header
.as_ref()
.is_some_and(|cached| cached != &data)
{
channel.gops.clear();
}
channel.video_sequence_header = Some(data.clone());
channel.video_timestamp = timestamp;
}
channel.gops.save_frame_data(
crate::rtmp::gop::FrameData::Video {
timestamp,
data: data.clone(),
},
is_keyframe,
);
}
ReceivedDataType::Audio => {
if is_sequence_header {
if channel
.audio_sequence_header
.as_ref()
.is_some_and(|cached| cached != &data)
{
channel.gops.clear();
}
channel.audio_sequence_header = Some(data.clone());
channel.audio_timestamp = timestamp;
}
channel.gops.save_frame_data(
crate::rtmp::gop::FrameData::Audio {
timestamp,
data: data.clone(),
},
false,
);
}
}
let MediaChannel {
watching_client_ids,
metadata,
fanout_serializer,
..
} = channel;
let channel_is_audio_only = metadata
.as_ref()
.is_some_and(|m| m.audio_codec_id.is_some() && m.video_codec_id.is_none());
let mut serialized_groups: Vec<(u32, Bytes)> = Vec::new();
for client_id in watching_client_ids.iter() {
let client = match self.clients.get_mut(*client_id) {
Some(client) => client,
None => continue,
};
let active_stream_id = match &client.current_action {
ClientAction::Watching {
stream_key: watched_stream_key,
stream_id,
} => {
if *watched_stream_key != stream_key {
debug!(
"Rtmp client {} is watching '{}'; skipping frame delivery \
from channel '{}'",
client_id, watched_stream_key, stream_key
);
continue;
}
*stream_id
}
_ => continue,
};
let should_send_to_client = should_send_to_watcher(
data_type,
client.has_received_video_keyframe,
is_keyframe,
is_sequence_header,
channel_is_audio_only,
);
if !should_send_to_client {
continue;
}
if is_video && is_keyframe {
client.has_received_video_keyframe = true;
}
let wire_bytes = match serialized_groups
.iter()
.find(|(stream_id, _)| *stream_id == active_stream_id)
{
Some((_, bytes)) => bytes.clone(),
None => match serialize_media(
fanout_serializer,
data_type,
active_stream_id,
data.clone(),
timestamp,
true,
) {
Ok(packet) => {
let bytes = Bytes::from(packet.bytes);
serialized_groups.push((active_stream_id, bytes.clone()));
bytes
}
Err(error) => {
let data_type_str = if is_video { "video" } else { "audio" };
debug!(
"Rtmp error serializing {} data for client on connection id {}: {:?}",
data_type_str, client.connection_id, error
);
server_results.push(ServerResult::DisconnectConnection {
connection_id: client.connection_id,
});
continue;
}
},
};
server_results.push(ServerResult::OutboundPacket {
target_connection_id: client.connection_id,
bytes: wire_bytes,
can_be_dropped: true,
is_keyframe,
is_sequence_header,
is_video,
});
}
}
}
pub(super) fn should_send_to_watcher(
data_type: ReceivedDataType,
has_received_video_keyframe: bool,
is_keyframe: bool,
is_sequence_header: bool,
channel_is_audio_only: bool,
) -> bool {
match data_type {
ReceivedDataType::Video => has_received_video_keyframe || is_sequence_header || is_keyframe,
ReceivedDataType::Audio => {
has_received_video_keyframe || is_sequence_header || channel_is_audio_only
}
}
}