2020-04-08 12:11:16 +00:00
|
|
|
// Copyright (C) 2020 Sebastian Dröge <sebastian@centricular.com>
|
|
|
|
//
|
2022-01-15 18:40:12 +00:00
|
|
|
// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0.
|
|
|
|
// If a copy of the MPL was not distributed with this file, You can obtain one at
|
|
|
|
// <https://mozilla.org/MPL/2.0/>.
|
2020-04-08 12:11:16 +00:00
|
|
|
//
|
2022-01-15 18:40:12 +00:00
|
|
|
// SPDX-License-Identifier: MPL-2.0
|
2020-04-08 12:11:16 +00:00
|
|
|
|
2021-06-03 18:20:54 +00:00
|
|
|
use gst::glib;
|
2020-04-08 12:11:16 +00:00
|
|
|
use gst::prelude::*;
|
|
|
|
use gst::subclass::prelude::*;
|
|
|
|
|
2021-09-13 15:08:45 +00:00
|
|
|
use parking_lot::Mutex;
|
2021-05-28 16:35:28 +00:00
|
|
|
use std::time::Instant;
|
2022-08-19 14:34:17 +00:00
|
|
|
use std::{cmp, mem};
|
2020-04-08 12:11:16 +00:00
|
|
|
|
2023-07-06 13:43:37 +00:00
|
|
|
use gst::glib::once_cell::sync::Lazy;
|
2020-04-08 12:11:16 +00:00
|
|
|
|
2020-11-15 12:08:54 +00:00
|
|
|
use super::custom_source::CustomSource;
|
|
|
|
use super::{RetryReason, Status};
|
|
|
|
|
2020-04-08 12:11:16 +00:00
|
|
|
static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
|
|
|
|
gst::DebugCategory::new(
|
|
|
|
"fallbacksrc",
|
|
|
|
gst::DebugColorFlags::empty(),
|
|
|
|
Some("Fallback Source Bin"),
|
|
|
|
)
|
|
|
|
});
|
|
|
|
|
2020-10-29 13:39:58 +00:00
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
struct Stats {
|
|
|
|
num_retry: u64,
|
2022-08-19 14:34:17 +00:00
|
|
|
num_fallback_retry: u64,
|
2020-10-29 13:39:58 +00:00
|
|
|
last_retry_reason: RetryReason,
|
2022-08-19 14:34:17 +00:00
|
|
|
last_fallback_retry_reason: RetryReason,
|
2020-10-29 13:39:58 +00:00
|
|
|
buffering_percent: i32,
|
2022-08-19 14:34:17 +00:00
|
|
|
fallback_buffering_percent: i32,
|
2020-10-29 13:39:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for Stats {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
|
|
|
num_retry: 0,
|
2022-08-19 14:34:17 +00:00
|
|
|
num_fallback_retry: 0,
|
2020-10-29 13:39:58 +00:00
|
|
|
last_retry_reason: RetryReason::None,
|
2022-08-19 14:34:17 +00:00
|
|
|
last_fallback_retry_reason: RetryReason::None,
|
2020-10-29 13:39:58 +00:00
|
|
|
buffering_percent: 100,
|
2022-08-19 14:34:17 +00:00
|
|
|
fallback_buffering_percent: 100,
|
2020-10-29 13:39:58 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Stats {
|
|
|
|
fn to_structure(&self) -> gst::Structure {
|
|
|
|
gst::Structure::builder("application/x-fallbacksrc-stats")
|
2021-11-06 07:34:10 +00:00
|
|
|
.field("num-retry", self.num_retry)
|
2022-11-01 08:27:48 +00:00
|
|
|
.field("num-fallback-retry", self.num_fallback_retry)
|
2021-11-06 07:34:10 +00:00
|
|
|
.field("last-retry-reason", self.last_retry_reason)
|
2022-08-19 14:34:17 +00:00
|
|
|
.field(
|
|
|
|
"last-fallback-retry-reason",
|
|
|
|
self.last_fallback_retry_reason,
|
|
|
|
)
|
2021-11-06 07:34:10 +00:00
|
|
|
.field("buffering-percent", self.buffering_percent)
|
2022-08-19 14:34:17 +00:00
|
|
|
.field(
|
|
|
|
"fallback-buffering-percent",
|
|
|
|
self.fallback_buffering_percent,
|
|
|
|
)
|
2020-10-29 13:39:58 +00:00
|
|
|
.build()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-08 12:11:16 +00:00
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
struct Settings {
|
|
|
|
enable_audio: bool,
|
|
|
|
enable_video: bool,
|
|
|
|
uri: Option<String>,
|
2020-05-19 13:51:59 +00:00
|
|
|
source: Option<gst::Element>,
|
2020-04-08 12:11:16 +00:00
|
|
|
fallback_uri: Option<String>,
|
2021-05-28 16:35:28 +00:00
|
|
|
timeout: gst::ClockTime,
|
|
|
|
restart_timeout: gst::ClockTime,
|
|
|
|
retry_timeout: gst::ClockTime,
|
2020-07-03 12:37:36 +00:00
|
|
|
restart_on_eos: bool,
|
2021-05-28 16:35:28 +00:00
|
|
|
min_latency: gst::ClockTime,
|
2020-07-10 08:14:33 +00:00
|
|
|
buffer_duration: i64,
|
2021-05-21 20:28:06 +00:00
|
|
|
immediate_fallback: bool,
|
2021-05-27 23:43:50 +00:00
|
|
|
manual_unblock: bool,
|
2022-05-25 17:46:46 +00:00
|
|
|
fallback_video_caps: gst::Caps,
|
|
|
|
fallback_audio_caps: gst::Caps,
|
2020-04-08 12:11:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for Settings {
|
|
|
|
fn default() -> Self {
|
|
|
|
Settings {
|
|
|
|
enable_audio: true,
|
|
|
|
enable_video: true,
|
|
|
|
uri: None,
|
2020-05-19 13:51:59 +00:00
|
|
|
source: None,
|
2020-04-08 12:11:16 +00:00
|
|
|
fallback_uri: None,
|
2022-10-17 17:48:43 +00:00
|
|
|
timeout: 5.seconds(),
|
|
|
|
restart_timeout: 5.seconds(),
|
|
|
|
retry_timeout: 60.seconds(),
|
2020-07-03 12:37:36 +00:00
|
|
|
restart_on_eos: false,
|
2021-05-28 16:35:28 +00:00
|
|
|
min_latency: gst::ClockTime::ZERO,
|
2020-07-10 08:14:33 +00:00
|
|
|
buffer_duration: -1,
|
2021-05-21 20:28:06 +00:00
|
|
|
immediate_fallback: false,
|
2021-05-27 23:43:50 +00:00
|
|
|
manual_unblock: false,
|
2022-05-25 17:46:46 +00:00
|
|
|
fallback_video_caps: gst::Caps::new_any(),
|
|
|
|
fallback_audio_caps: gst::Caps::new_any(),
|
2020-04-08 12:11:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-19 13:51:59 +00:00
|
|
|
#[derive(Debug)]
|
|
|
|
enum Source {
|
|
|
|
Uri(String),
|
|
|
|
Element(gst::Element),
|
|
|
|
}
|
|
|
|
|
|
|
|
// Blocking buffer pad probe on the source pads. Once blocked we have a running time for the
|
2020-04-08 12:11:16 +00:00
|
|
|
// current buffer that can later be used for offsetting
|
|
|
|
//
|
|
|
|
// This is used for the initial offsetting after starting of the stream and for "pausing" when
|
|
|
|
// buffering.
|
|
|
|
struct Block {
|
|
|
|
pad: gst::Pad,
|
|
|
|
probe_id: gst::PadProbeId,
|
2022-10-19 14:03:15 +00:00
|
|
|
qos_probe_id: gst::PadProbeId,
|
2021-05-28 16:35:28 +00:00
|
|
|
running_time: Option<gst::ClockTime>,
|
2020-04-08 12:11:16 +00:00
|
|
|
}
|
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
struct StreamBranch {
|
|
|
|
// source pad from actual source inside the source bin
|
|
|
|
source_srcpad: gst::Pad,
|
|
|
|
// blocking pad probe on the source pad of the source queue
|
2020-05-19 13:51:59 +00:00
|
|
|
source_srcpad_block: Option<Block>,
|
2020-04-08 12:11:16 +00:00
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
// other elements in the source bin before the ghostpad
|
2020-04-08 12:11:16 +00:00
|
|
|
clocksync: gst::Element,
|
2022-08-19 14:34:17 +00:00
|
|
|
converters: gst::Element,
|
|
|
|
queue: gst::Element,
|
|
|
|
// queue source pad, target pad of the source ghost pad
|
|
|
|
queue_srcpad: gst::Pad,
|
2020-04-08 12:11:16 +00:00
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
// Request pad on the fallbackswitch
|
|
|
|
switch_pad: gst::Pad,
|
|
|
|
}
|
2022-04-12 11:37:29 +00:00
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
// Connects one source pad with fallbackswitch and the corresponding fallback input
|
|
|
|
struct Stream {
|
|
|
|
// Main stream and fallback stream branches to the fallback switch
|
|
|
|
main_branch: Option<StreamBranch>,
|
|
|
|
// If this does not exist then the fallbackswitch is connected directly to the dummy
|
|
|
|
// audio/video sources
|
|
|
|
fallback_branch: Option<StreamBranch>,
|
2020-07-30 11:05:08 +00:00
|
|
|
|
2020-04-08 12:11:16 +00:00
|
|
|
// fallbackswitch
|
2022-08-19 14:34:17 +00:00
|
|
|
// fallbackswitch in the main bin, linked to the ghostpads above
|
2020-04-08 12:11:16 +00:00
|
|
|
switch: gst::Element,
|
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
// output source pad on the main bin, switch source pad is ghostpad target
|
2020-07-02 08:27:57 +00:00
|
|
|
srcpad: gst::GhostPad,
|
2022-08-19 14:34:17 +00:00
|
|
|
|
|
|
|
// filter caps for the fallback/dummy streams
|
|
|
|
filter_caps: gst::Caps,
|
2020-04-08 12:11:16 +00:00
|
|
|
}
|
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
struct SourceBin {
|
|
|
|
// uridecodebin3 or custom source element inside a bin.
|
|
|
|
//
|
|
|
|
// This bin would also contain imagefreeze, clocksync and queue elements as needed for the
|
|
|
|
// outputs and would be connected via ghost pads to the fallbackswitch elements.
|
|
|
|
source: gst::Bin,
|
|
|
|
pending_restart: bool,
|
|
|
|
is_live: bool,
|
|
|
|
is_image: bool,
|
2020-05-21 07:20:16 +00:00
|
|
|
|
2020-07-02 09:23:48 +00:00
|
|
|
// For timing out the source and shutting it down to restart it
|
2022-08-19 14:34:17 +00:00
|
|
|
restart_timeout: Option<gst::SingleShotClockId>,
|
2020-04-08 12:11:16 +00:00
|
|
|
// For restarting the source after shutting it down
|
2022-08-19 14:34:17 +00:00
|
|
|
pending_restart_timeout: Option<gst::SingleShotClockId>,
|
2020-04-08 12:11:16 +00:00
|
|
|
// For failing completely if we didn't recover after the retry timeout
|
2022-08-19 14:34:17 +00:00
|
|
|
retry_timeout: Option<gst::SingleShotClockId>,
|
|
|
|
|
|
|
|
// Stream collection posted by source
|
|
|
|
streams: Option<gst::StreamCollection>,
|
|
|
|
}
|
|
|
|
|
|
|
|
struct State {
|
|
|
|
source: SourceBin,
|
|
|
|
fallback_source: Option<SourceBin>,
|
|
|
|
|
|
|
|
// audio/video dummy source if the fallback source fails or is not started yet
|
|
|
|
audio_dummy_source: Option<gst::Bin>,
|
|
|
|
video_dummy_source: Option<gst::Bin>,
|
2020-04-08 12:11:16 +00:00
|
|
|
|
|
|
|
// All our output streams, selected by properties
|
|
|
|
video_stream: Option<Stream>,
|
|
|
|
audio_stream: Option<Stream>,
|
|
|
|
flow_combiner: gst_base::UniqueFlowCombiner,
|
|
|
|
|
|
|
|
last_buffering_update: Option<Instant>,
|
2022-08-19 14:34:17 +00:00
|
|
|
fallback_last_buffering_update: Option<Instant>,
|
2020-04-08 12:11:16 +00:00
|
|
|
|
|
|
|
// Configure settings
|
|
|
|
settings: Settings,
|
2020-05-19 13:51:59 +00:00
|
|
|
configured_source: Source,
|
2020-10-29 13:39:58 +00:00
|
|
|
|
|
|
|
// Statistics
|
|
|
|
stats: Stats,
|
2021-05-27 23:43:50 +00:00
|
|
|
|
|
|
|
// When application is using the manual-unblock property
|
|
|
|
manually_blocked: bool,
|
|
|
|
// So that we don't schedule a restart when manually unblocking
|
|
|
|
// and our source hasn't reached the required state
|
|
|
|
schedule_restart_on_unblock: bool,
|
2020-04-08 12:11:16 +00:00
|
|
|
}
|
|
|
|
|
2021-03-07 16:22:24 +00:00
|
|
|
#[derive(Default)]
|
2020-11-15 12:08:54 +00:00
|
|
|
pub struct FallbackSrc {
|
2020-04-08 12:11:16 +00:00
|
|
|
settings: Mutex<Settings>,
|
|
|
|
state: Mutex<Option<State>>,
|
|
|
|
}
|
|
|
|
|
2021-03-07 16:22:24 +00:00
|
|
|
#[glib::object_subclass]
|
2020-04-08 12:11:16 +00:00
|
|
|
impl ObjectSubclass for FallbackSrc {
|
2022-10-23 15:42:58 +00:00
|
|
|
const NAME: &'static str = "GstFallbackSrc";
|
2020-11-15 12:08:54 +00:00
|
|
|
type Type = super::FallbackSrc;
|
2020-04-08 12:11:16 +00:00
|
|
|
type ParentType = gst::Bin;
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ObjectImpl for FallbackSrc {
|
2021-01-21 18:21:29 +00:00
|
|
|
fn properties() -> &'static [glib::ParamSpec] {
|
|
|
|
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
|
|
|
vec![
|
2022-08-18 12:04:15 +00:00
|
|
|
glib::ParamSpecBoolean::builder("enable-audio")
|
|
|
|
.nick("Enable Audio")
|
|
|
|
.blurb("Enable the audio stream, this will output silence if there's no audio in the configured URI")
|
|
|
|
.default_value(true)
|
|
|
|
.mutable_ready()
|
|
|
|
.build(),
|
|
|
|
glib::ParamSpecBoolean::builder("enable-video")
|
|
|
|
.nick("Enable Video")
|
|
|
|
.blurb("Enable the video stream, this will output black or the fallback video if there's no video in the configured URI")
|
|
|
|
.default_value(true)
|
|
|
|
.mutable_ready()
|
|
|
|
.build(),
|
|
|
|
glib::ParamSpecString::builder("uri")
|
|
|
|
.nick("URI")
|
|
|
|
.blurb("URI to use")
|
|
|
|
.mutable_ready()
|
|
|
|
.build(),
|
2022-09-05 08:45:47 +00:00
|
|
|
glib::ParamSpecObject::builder::<gst::Element>("source")
|
2022-08-18 12:04:15 +00:00
|
|
|
.nick("Source")
|
|
|
|
.blurb("Source to use instead of the URI")
|
|
|
|
.mutable_ready()
|
|
|
|
.build(),
|
|
|
|
glib::ParamSpecString::builder("fallback-uri")
|
|
|
|
.nick("Fallback URI")
|
|
|
|
.blurb("Fallback URI to use for video in case the main stream doesn't work")
|
|
|
|
.mutable_ready()
|
|
|
|
.build(),
|
|
|
|
glib::ParamSpecUInt64::builder("timeout")
|
|
|
|
.nick("Timeout")
|
|
|
|
.blurb("Timeout for switching to the fallback URI")
|
|
|
|
.maximum(std::u64::MAX - 1)
|
|
|
|
.default_value(5 * *gst::ClockTime::SECOND)
|
|
|
|
.mutable_ready()
|
|
|
|
.build(),
|
|
|
|
glib::ParamSpecUInt64::builder("restart-timeout")
|
|
|
|
.nick("Timeout")
|
|
|
|
.blurb("Timeout for restarting an active source")
|
|
|
|
.maximum(std::u64::MAX - 1)
|
|
|
|
.default_value(5 * *gst::ClockTime::SECOND)
|
|
|
|
.mutable_ready()
|
|
|
|
.build(),
|
|
|
|
glib::ParamSpecUInt64::builder("retry-timeout")
|
|
|
|
.nick("Retry Timeout")
|
|
|
|
.blurb("Timeout for stopping after repeated failure")
|
|
|
|
.maximum(std::u64::MAX - 1)
|
|
|
|
.default_value(60 * *gst::ClockTime::SECOND)
|
|
|
|
.mutable_ready()
|
|
|
|
.build(),
|
|
|
|
glib::ParamSpecBoolean::builder("restart-on-eos")
|
|
|
|
.nick("Restart on EOS")
|
|
|
|
.blurb("Restart source on EOS")
|
|
|
|
.default_value(false)
|
|
|
|
.mutable_ready()
|
|
|
|
.build(),
|
2023-01-21 16:13:48 +00:00
|
|
|
glib::ParamSpecEnum::builder_with_default("status", Status::Stopped)
|
2022-08-18 12:04:15 +00:00
|
|
|
.nick("Status")
|
|
|
|
.blurb("Current source status")
|
|
|
|
.read_only()
|
|
|
|
.build(),
|
|
|
|
glib::ParamSpecUInt64::builder("min-latency")
|
|
|
|
.nick("Minimum Latency")
|
|
|
|
.blurb("When the main source has a higher latency than the fallback source \
|
2021-01-21 18:21:29 +00:00
|
|
|
this allows to configure a minimum latency that would be configured \
|
2022-08-18 12:04:15 +00:00
|
|
|
if initially the fallback is enabled")
|
|
|
|
.maximum(std::u64::MAX - 1)
|
|
|
|
.mutable_ready()
|
|
|
|
.build(),
|
|
|
|
glib::ParamSpecInt64::builder("buffer-duration")
|
|
|
|
.nick("Buffer Duration")
|
|
|
|
.blurb("Buffer duration when buffering streams (-1 default value)")
|
|
|
|
.minimum(-1)
|
|
|
|
.maximum(std::i64::MAX - 1)
|
|
|
|
.default_value(-1)
|
|
|
|
.mutable_ready()
|
|
|
|
.build(),
|
2022-09-05 08:45:47 +00:00
|
|
|
glib::ParamSpecBoxed::builder::<gst::Structure>("statistics")
|
2022-08-18 12:04:15 +00:00
|
|
|
.nick("Statistics")
|
|
|
|
.blurb("Various statistics")
|
|
|
|
.read_only()
|
|
|
|
.build(),
|
|
|
|
glib::ParamSpecBoolean::builder("manual-unblock")
|
|
|
|
.nick("Manual unblock")
|
|
|
|
.blurb("When enabled, the application must call the unblock signal, except for live streams")
|
|
|
|
.default_value(false)
|
|
|
|
.mutable_ready()
|
|
|
|
.build(),
|
|
|
|
glib::ParamSpecBoolean::builder("immediate-fallback")
|
|
|
|
.nick("Immediate fallback")
|
|
|
|
.blurb("Forward the fallback streams immediately at startup, when the primary streams are slow to start up and immediate output is required")
|
|
|
|
.default_value(false)
|
|
|
|
.mutable_ready()
|
|
|
|
.build(),
|
2022-09-05 08:45:47 +00:00
|
|
|
glib::ParamSpecBoxed::builder::<gst::Caps>("fallback-video-caps")
|
2022-08-18 12:04:15 +00:00
|
|
|
.nick("Fallback Video Caps")
|
|
|
|
.blurb("Raw video caps for fallback stream")
|
|
|
|
.mutable_ready()
|
|
|
|
.build(),
|
2022-09-05 08:45:47 +00:00
|
|
|
glib::ParamSpecBoxed::builder::<gst::Caps>("fallback-audio-caps")
|
2022-08-18 12:04:15 +00:00
|
|
|
.nick("Fallback Audio Caps")
|
|
|
|
.blurb("Raw audio caps for fallback stream")
|
|
|
|
.mutable_ready()
|
|
|
|
.build(),
|
2021-01-21 18:21:29 +00:00
|
|
|
]
|
|
|
|
});
|
|
|
|
|
|
|
|
PROPERTIES.as_ref()
|
|
|
|
}
|
2020-04-08 12:11:16 +00:00
|
|
|
|
2022-10-09 13:06:59 +00:00
|
|
|
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
|
2021-04-12 12:49:54 +00:00
|
|
|
match pspec.name() {
|
2021-01-21 18:21:29 +00:00
|
|
|
"enable-audio" => {
|
2021-09-13 15:08:45 +00:00
|
|
|
let mut settings = self.settings.lock();
|
2021-04-25 12:41:22 +00:00
|
|
|
let new_value = value.get().expect("type checked upstream");
|
2022-02-21 17:43:46 +00:00
|
|
|
gst::info!(
|
2020-04-08 12:11:16 +00:00
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: self,
|
2020-04-08 12:11:16 +00:00
|
|
|
"Changing enable-audio from {:?} to {:?}",
|
|
|
|
settings.enable_audio,
|
|
|
|
new_value,
|
|
|
|
);
|
|
|
|
settings.enable_audio = new_value;
|
|
|
|
}
|
2021-01-21 18:21:29 +00:00
|
|
|
"enable-video" => {
|
2021-09-13 15:08:45 +00:00
|
|
|
let mut settings = self.settings.lock();
|
2021-04-25 12:41:22 +00:00
|
|
|
let new_value = value.get().expect("type checked upstream");
|
2022-02-21 17:43:46 +00:00
|
|
|
gst::info!(
|
2020-04-08 12:11:16 +00:00
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: self,
|
2020-04-08 12:11:16 +00:00
|
|
|
"Changing enable-video from {:?} to {:?}",
|
|
|
|
settings.enable_video,
|
|
|
|
new_value,
|
|
|
|
);
|
|
|
|
settings.enable_video = new_value;
|
|
|
|
}
|
2021-01-21 18:21:29 +00:00
|
|
|
"uri" => {
|
2021-09-13 15:08:45 +00:00
|
|
|
let mut settings = self.settings.lock();
|
2020-04-08 12:11:16 +00:00
|
|
|
let new_value = value.get().expect("type checked upstream");
|
2022-02-21 17:43:46 +00:00
|
|
|
gst::info!(
|
2020-04-08 12:11:16 +00:00
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: self,
|
2020-04-08 12:11:16 +00:00
|
|
|
"Changing URI from {:?} to {:?}",
|
|
|
|
settings.uri,
|
|
|
|
new_value,
|
|
|
|
);
|
|
|
|
settings.uri = new_value;
|
|
|
|
}
|
2021-01-21 18:21:29 +00:00
|
|
|
"source" => {
|
2021-09-13 15:08:45 +00:00
|
|
|
let mut settings = self.settings.lock();
|
2020-05-19 13:51:59 +00:00
|
|
|
let new_value = value.get().expect("type checked upstream");
|
2022-02-21 17:43:46 +00:00
|
|
|
gst::info!(
|
2020-05-19 13:51:59 +00:00
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: self,
|
2020-05-19 13:51:59 +00:00
|
|
|
"Changing source from {:?} to {:?}",
|
|
|
|
settings.source,
|
|
|
|
new_value,
|
|
|
|
);
|
|
|
|
settings.source = new_value;
|
|
|
|
}
|
2021-01-21 18:21:29 +00:00
|
|
|
"fallback-uri" => {
|
2021-09-13 15:08:45 +00:00
|
|
|
let mut settings = self.settings.lock();
|
2020-04-08 12:11:16 +00:00
|
|
|
let new_value = value.get().expect("type checked upstream");
|
2022-02-21 17:43:46 +00:00
|
|
|
gst::info!(
|
2020-04-08 12:11:16 +00:00
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: self,
|
2020-04-08 12:11:16 +00:00
|
|
|
"Changing Fallback URI from {:?} to {:?}",
|
|
|
|
settings.fallback_uri,
|
|
|
|
new_value,
|
|
|
|
);
|
|
|
|
settings.fallback_uri = new_value;
|
|
|
|
}
|
2021-01-21 18:21:29 +00:00
|
|
|
"timeout" => {
|
2021-09-13 15:08:45 +00:00
|
|
|
let mut settings = self.settings.lock();
|
2021-04-25 12:41:22 +00:00
|
|
|
let new_value = value.get().expect("type checked upstream");
|
2022-02-21 17:43:46 +00:00
|
|
|
gst::info!(
|
2020-04-08 12:11:16 +00:00
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: self,
|
2020-04-08 12:11:16 +00:00
|
|
|
"Changing timeout from {:?} to {:?}",
|
|
|
|
settings.timeout,
|
|
|
|
new_value,
|
|
|
|
);
|
|
|
|
settings.timeout = new_value;
|
|
|
|
}
|
2021-01-21 18:21:29 +00:00
|
|
|
"restart-timeout" => {
|
2021-09-13 15:08:45 +00:00
|
|
|
let mut settings = self.settings.lock();
|
2021-04-25 12:41:22 +00:00
|
|
|
let new_value = value.get().expect("type checked upstream");
|
2022-02-21 17:43:46 +00:00
|
|
|
gst::info!(
|
2020-07-02 09:23:48 +00:00
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: self,
|
2020-07-02 09:23:48 +00:00
|
|
|
"Changing Restart Timeout from {:?} to {:?}",
|
|
|
|
settings.restart_timeout,
|
|
|
|
new_value,
|
|
|
|
);
|
|
|
|
settings.restart_timeout = new_value;
|
|
|
|
}
|
2021-01-21 18:21:29 +00:00
|
|
|
"retry-timeout" => {
|
2021-09-13 15:08:45 +00:00
|
|
|
let mut settings = self.settings.lock();
|
2021-04-25 12:41:22 +00:00
|
|
|
let new_value = value.get().expect("type checked upstream");
|
2022-02-21 17:43:46 +00:00
|
|
|
gst::info!(
|
2020-04-08 12:11:16 +00:00
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: self,
|
2020-04-08 12:11:16 +00:00
|
|
|
"Changing Retry Timeout from {:?} to {:?}",
|
|
|
|
settings.retry_timeout,
|
|
|
|
new_value,
|
|
|
|
);
|
|
|
|
settings.retry_timeout = new_value;
|
|
|
|
}
|
2021-01-21 18:21:29 +00:00
|
|
|
"restart-on-eos" => {
|
2021-09-13 15:08:45 +00:00
|
|
|
let mut settings = self.settings.lock();
|
2021-04-25 12:41:22 +00:00
|
|
|
let new_value = value.get().expect("type checked upstream");
|
2022-02-21 17:43:46 +00:00
|
|
|
gst::info!(
|
2020-07-03 12:37:36 +00:00
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: self,
|
2020-07-03 12:37:36 +00:00
|
|
|
"Changing restart-on-eos from {:?} to {:?}",
|
|
|
|
settings.restart_on_eos,
|
|
|
|
new_value,
|
|
|
|
);
|
|
|
|
settings.restart_on_eos = new_value;
|
|
|
|
}
|
2021-01-21 18:21:29 +00:00
|
|
|
"min-latency" => {
|
2021-09-13 15:08:45 +00:00
|
|
|
let mut settings = self.settings.lock();
|
2021-04-25 12:41:22 +00:00
|
|
|
let new_value = value.get().expect("type checked upstream");
|
2022-02-21 17:43:46 +00:00
|
|
|
gst::info!(
|
2020-07-08 10:40:03 +00:00
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: self,
|
2020-07-08 10:40:03 +00:00
|
|
|
"Changing Minimum Latency from {:?} to {:?}",
|
|
|
|
settings.min_latency,
|
|
|
|
new_value,
|
|
|
|
);
|
|
|
|
settings.min_latency = new_value;
|
|
|
|
}
|
2021-01-21 18:21:29 +00:00
|
|
|
"buffer-duration" => {
|
2021-09-13 15:08:45 +00:00
|
|
|
let mut settings = self.settings.lock();
|
2021-04-25 12:41:22 +00:00
|
|
|
let new_value = value.get().expect("type checked upstream");
|
2022-02-21 17:43:46 +00:00
|
|
|
gst::info!(
|
2020-07-10 08:14:33 +00:00
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: self,
|
2020-07-10 08:14:33 +00:00
|
|
|
"Changing Buffer Duration from {:?} to {:?}",
|
|
|
|
settings.buffer_duration,
|
|
|
|
new_value,
|
|
|
|
);
|
|
|
|
settings.buffer_duration = new_value;
|
|
|
|
}
|
2021-05-21 20:28:06 +00:00
|
|
|
"immediate-fallback" => {
|
2021-09-13 15:08:45 +00:00
|
|
|
let mut settings = self.settings.lock();
|
2021-05-21 20:28:06 +00:00
|
|
|
let new_value = value.get().expect("type checked upstream");
|
2022-02-21 17:43:46 +00:00
|
|
|
gst::info!(
|
2021-05-21 20:28:06 +00:00
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: self,
|
2021-05-21 20:28:06 +00:00
|
|
|
"Changing immediate-fallback from {:?} to {:?}",
|
|
|
|
settings.immediate_fallback,
|
|
|
|
new_value,
|
|
|
|
);
|
|
|
|
settings.immediate_fallback = new_value;
|
|
|
|
}
|
2021-05-27 23:43:50 +00:00
|
|
|
"manual-unblock" => {
|
2021-09-13 15:08:45 +00:00
|
|
|
let mut settings = self.settings.lock();
|
2021-05-27 23:43:50 +00:00
|
|
|
let new_value = value.get().expect("type checked upstream");
|
2022-02-21 17:43:46 +00:00
|
|
|
gst::info!(
|
2021-05-27 23:43:50 +00:00
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: self,
|
2021-05-27 23:43:50 +00:00
|
|
|
"Changing manual-unblock from {:?} to {:?}",
|
|
|
|
settings.manual_unblock,
|
|
|
|
new_value,
|
|
|
|
);
|
|
|
|
settings.manual_unblock = new_value;
|
|
|
|
}
|
2022-05-25 17:46:46 +00:00
|
|
|
"fallback-video-caps" => {
|
|
|
|
let mut settings = self.settings.lock();
|
|
|
|
let new_value = value
|
|
|
|
.get::<Option<gst::Caps>>()
|
|
|
|
.expect("type checked upstream")
|
|
|
|
.unwrap_or_else(gst::Caps::new_any);
|
|
|
|
gst::info!(
|
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: self,
|
2022-05-25 17:46:46 +00:00
|
|
|
"Changing fallback video caps from {} to {}",
|
|
|
|
settings.fallback_video_caps,
|
|
|
|
new_value,
|
|
|
|
);
|
|
|
|
settings.fallback_video_caps = new_value;
|
|
|
|
}
|
|
|
|
"fallback-audio-caps" => {
|
|
|
|
let mut settings = self.settings.lock();
|
|
|
|
let new_value = value
|
|
|
|
.get::<Option<gst::Caps>>()
|
|
|
|
.expect("type checked upstream")
|
|
|
|
.unwrap_or_else(gst::Caps::new_any);
|
|
|
|
gst::info!(
|
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: self,
|
2022-05-25 17:46:46 +00:00
|
|
|
"Changing fallback audio caps from {} to {}",
|
|
|
|
settings.fallback_audio_caps,
|
|
|
|
new_value,
|
|
|
|
);
|
|
|
|
settings.fallback_audio_caps = new_value;
|
|
|
|
}
|
2020-04-08 12:11:16 +00:00
|
|
|
_ => unimplemented!(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Called whenever a value of a property is read. It can be called
|
|
|
|
// at any time from any thread.
|
2020-07-28 14:00:48 +00:00
|
|
|
#[allow(clippy::blocks_in_if_conditions)]
|
2022-10-09 13:06:59 +00:00
|
|
|
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
2021-04-12 12:49:54 +00:00
|
|
|
match pspec.name() {
|
2021-01-21 18:21:29 +00:00
|
|
|
"enable-audio" => {
|
2021-09-13 15:08:45 +00:00
|
|
|
let settings = self.settings.lock();
|
2020-11-19 15:55:57 +00:00
|
|
|
settings.enable_audio.to_value()
|
2020-04-08 12:11:16 +00:00
|
|
|
}
|
2021-01-21 18:21:29 +00:00
|
|
|
"enable-video" => {
|
2021-09-13 15:08:45 +00:00
|
|
|
let settings = self.settings.lock();
|
2020-11-19 15:55:57 +00:00
|
|
|
settings.enable_video.to_value()
|
2020-04-08 12:11:16 +00:00
|
|
|
}
|
2021-01-21 18:21:29 +00:00
|
|
|
"uri" => {
|
2021-09-13 15:08:45 +00:00
|
|
|
let settings = self.settings.lock();
|
2020-11-19 15:55:57 +00:00
|
|
|
settings.uri.to_value()
|
2020-04-08 12:11:16 +00:00
|
|
|
}
|
2021-01-21 18:21:29 +00:00
|
|
|
"source" => {
|
2021-09-13 15:08:45 +00:00
|
|
|
let settings = self.settings.lock();
|
2020-11-19 15:55:57 +00:00
|
|
|
settings.source.to_value()
|
2020-05-19 13:51:59 +00:00
|
|
|
}
|
2021-01-21 18:21:29 +00:00
|
|
|
"fallback-uri" => {
|
2021-09-13 15:08:45 +00:00
|
|
|
let settings = self.settings.lock();
|
2020-11-19 15:55:57 +00:00
|
|
|
settings.fallback_uri.to_value()
|
2020-04-08 12:11:16 +00:00
|
|
|
}
|
2021-01-21 18:21:29 +00:00
|
|
|
"timeout" => {
|
2021-09-13 15:08:45 +00:00
|
|
|
let settings = self.settings.lock();
|
2020-11-19 15:55:57 +00:00
|
|
|
settings.timeout.to_value()
|
2020-04-08 12:11:16 +00:00
|
|
|
}
|
2021-01-21 18:21:29 +00:00
|
|
|
"restart-timeout" => {
|
2021-09-13 15:08:45 +00:00
|
|
|
let settings = self.settings.lock();
|
2020-11-19 15:55:57 +00:00
|
|
|
settings.restart_timeout.to_value()
|
2020-07-02 09:23:48 +00:00
|
|
|
}
|
2021-01-21 18:21:29 +00:00
|
|
|
"retry-timeout" => {
|
2021-09-13 15:08:45 +00:00
|
|
|
let settings = self.settings.lock();
|
2020-11-19 15:55:57 +00:00
|
|
|
settings.retry_timeout.to_value()
|
2020-04-08 12:11:16 +00:00
|
|
|
}
|
2021-01-21 18:21:29 +00:00
|
|
|
"restart-on-eos" => {
|
2021-09-13 15:08:45 +00:00
|
|
|
let settings = self.settings.lock();
|
2020-11-19 15:55:57 +00:00
|
|
|
settings.restart_on_eos.to_value()
|
2020-07-03 12:37:36 +00:00
|
|
|
}
|
2021-01-21 18:21:29 +00:00
|
|
|
"status" => {
|
2021-09-13 15:08:45 +00:00
|
|
|
let state_guard = self.state.lock();
|
2020-04-08 12:11:16 +00:00
|
|
|
|
|
|
|
// If we have no state then we'r stopped
|
|
|
|
let state = match &*state_guard {
|
2020-11-19 15:55:57 +00:00
|
|
|
None => return Status::Stopped.to_value(),
|
2020-04-08 12:11:16 +00:00
|
|
|
Some(ref state) => state,
|
|
|
|
};
|
|
|
|
|
|
|
|
// If any restarts/retries are pending, we're retrying
|
2022-08-19 14:34:17 +00:00
|
|
|
if state.source.pending_restart
|
|
|
|
|| state.source.pending_restart_timeout.is_some()
|
|
|
|
|| state.source.retry_timeout.is_some()
|
2020-04-08 12:11:16 +00:00
|
|
|
{
|
2020-11-19 15:55:57 +00:00
|
|
|
return Status::Retrying.to_value();
|
2020-04-08 12:11:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise if buffering < 100, we have no streams yet or of the expected
|
2020-05-19 13:51:59 +00:00
|
|
|
// streams there is no source pad yet, we're buffering
|
2020-04-08 12:11:16 +00:00
|
|
|
let mut have_audio = false;
|
|
|
|
let mut have_video = false;
|
2022-08-19 14:34:17 +00:00
|
|
|
if let Some(ref streams) = state.source.streams {
|
2020-04-08 12:11:16 +00:00
|
|
|
for stream in streams.iter() {
|
|
|
|
have_audio =
|
2021-04-12 12:49:54 +00:00
|
|
|
have_audio || stream.stream_type().contains(gst::StreamType::AUDIO);
|
2020-04-08 12:11:16 +00:00
|
|
|
have_video =
|
2021-04-12 12:49:54 +00:00
|
|
|
have_video || stream.stream_type().contains(gst::StreamType::VIDEO);
|
2020-04-08 12:11:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-29 13:39:58 +00:00
|
|
|
if state.stats.buffering_percent < 100
|
2022-08-19 14:34:17 +00:00
|
|
|
|| state.source.restart_timeout.is_some()
|
|
|
|
|| state.source.streams.is_none()
|
2020-04-08 12:11:16 +00:00
|
|
|
|| (have_audio
|
|
|
|
&& state
|
|
|
|
.audio_stream
|
|
|
|
.as_ref()
|
2022-08-19 14:34:17 +00:00
|
|
|
.and_then(|s| s.main_branch.as_ref())
|
|
|
|
.map(|b| b.source_srcpad_block.is_some())
|
2020-07-02 15:16:35 +00:00
|
|
|
.unwrap_or(true))
|
2020-04-08 12:11:16 +00:00
|
|
|
|| (have_video
|
|
|
|
&& state
|
|
|
|
.video_stream
|
|
|
|
.as_ref()
|
2022-08-19 14:34:17 +00:00
|
|
|
.and_then(|s| s.main_branch.as_ref())
|
|
|
|
.map(|b| b.source_srcpad_block.is_some())
|
2020-07-02 15:16:35 +00:00
|
|
|
.unwrap_or(true))
|
2020-04-08 12:11:16 +00:00
|
|
|
{
|
2020-11-19 15:55:57 +00:00
|
|
|
return Status::Buffering.to_value();
|
2020-04-08 12:11:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise we're running now
|
2020-11-19 15:55:57 +00:00
|
|
|
Status::Running.to_value()
|
2020-04-08 12:11:16 +00:00
|
|
|
}
|
2021-01-21 18:21:29 +00:00
|
|
|
"min-latency" => {
|
2021-09-13 15:08:45 +00:00
|
|
|
let settings = self.settings.lock();
|
2020-11-19 15:55:57 +00:00
|
|
|
settings.min_latency.to_value()
|
2020-07-08 10:40:03 +00:00
|
|
|
}
|
2021-01-21 18:21:29 +00:00
|
|
|
"buffer-duration" => {
|
2021-09-13 15:08:45 +00:00
|
|
|
let settings = self.settings.lock();
|
2020-11-19 15:55:57 +00:00
|
|
|
settings.buffer_duration.to_value()
|
2020-07-10 08:14:33 +00:00
|
|
|
}
|
2021-04-12 12:49:54 +00:00
|
|
|
"statistics" => self.stats().to_value(),
|
2021-05-21 20:28:06 +00:00
|
|
|
"immediate-fallback" => {
|
2021-09-13 15:08:45 +00:00
|
|
|
let settings = self.settings.lock();
|
2021-05-21 20:28:06 +00:00
|
|
|
settings.immediate_fallback.to_value()
|
|
|
|
}
|
2021-05-27 23:43:50 +00:00
|
|
|
"manual-unblock" => {
|
2021-09-13 15:08:45 +00:00
|
|
|
let settings = self.settings.lock();
|
2021-05-27 23:43:50 +00:00
|
|
|
settings.manual_unblock.to_value()
|
|
|
|
}
|
2022-05-25 17:46:46 +00:00
|
|
|
"fallback-video-caps" => {
|
|
|
|
let settings = self.settings.lock();
|
|
|
|
settings.fallback_video_caps.to_value()
|
|
|
|
}
|
|
|
|
"fallback-audio-caps" => {
|
|
|
|
let settings = self.settings.lock();
|
|
|
|
settings.fallback_audio_caps.to_value()
|
|
|
|
}
|
2020-04-08 12:11:16 +00:00
|
|
|
_ => unimplemented!(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-21 18:21:29 +00:00
|
|
|
fn signals() -> &'static [glib::subclass::Signal] {
|
|
|
|
static SIGNALS: Lazy<Vec<glib::subclass::Signal>> = Lazy::new(|| {
|
2021-05-27 23:43:50 +00:00
|
|
|
vec![
|
2022-07-21 15:09:17 +00:00
|
|
|
glib::subclass::Signal::builder("update-uri")
|
2022-08-17 20:37:39 +00:00
|
|
|
.param_types([String::static_type()])
|
2022-07-21 15:09:17 +00:00
|
|
|
.return_type::<String>()
|
|
|
|
.class_handler(|_token, args| {
|
|
|
|
// Simply return the input by default
|
|
|
|
Some(args[1].clone())
|
|
|
|
})
|
|
|
|
.accumulator(|_hint, ret, value| {
|
|
|
|
// First signal handler wins
|
|
|
|
*ret = value.clone();
|
|
|
|
false
|
|
|
|
})
|
|
|
|
.build(),
|
|
|
|
glib::subclass::Signal::builder("unblock")
|
2021-05-27 23:43:50 +00:00
|
|
|
.action()
|
|
|
|
.class_handler(|_token, args| {
|
|
|
|
let element = args[0].get::<super::FallbackSrc>().expect("signal arg");
|
2022-10-09 13:06:59 +00:00
|
|
|
let imp = element.imp();
|
|
|
|
let mut state_guard = imp.state.lock();
|
2021-05-27 23:43:50 +00:00
|
|
|
let state = match &mut *state_guard {
|
|
|
|
None => {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
Some(state) => state,
|
|
|
|
};
|
|
|
|
|
|
|
|
state.manually_blocked = false;
|
|
|
|
|
2022-10-09 13:06:59 +00:00
|
|
|
if state.schedule_restart_on_unblock && imp.have_fallback_activated(state) {
|
|
|
|
imp.schedule_source_restart_timeout(state, gst::ClockTime::ZERO, false);
|
2021-05-27 23:43:50 +00:00
|
|
|
}
|
|
|
|
|
2022-10-09 13:06:59 +00:00
|
|
|
imp.unblock_pads(state, false);
|
2021-05-27 23:43:50 +00:00
|
|
|
|
|
|
|
None
|
|
|
|
})
|
|
|
|
.build(),
|
|
|
|
]
|
2021-01-21 18:21:29 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
SIGNALS.as_ref()
|
|
|
|
}
|
|
|
|
|
2022-10-09 13:06:59 +00:00
|
|
|
fn constructed(&self) {
|
|
|
|
self.parent_constructed();
|
2020-04-08 12:11:16 +00:00
|
|
|
|
2022-10-23 20:03:22 +00:00
|
|
|
let obj = self.obj();
|
2020-11-15 12:08:54 +00:00
|
|
|
obj.set_suppressed_flags(gst::ElementFlags::SOURCE | gst::ElementFlags::SINK);
|
|
|
|
obj.set_element_flags(gst::ElementFlags::SOURCE);
|
|
|
|
obj.set_bin_flags(gst::BinFlags::STREAMS_AWARE);
|
2020-04-08 12:11:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-23 08:57:31 +00:00
|
|
|
impl GstObjectImpl for FallbackSrc {}
|
|
|
|
|
2020-04-08 12:11:16 +00:00
|
|
|
impl ElementImpl for FallbackSrc {
|
2021-01-21 18:21:29 +00:00
|
|
|
fn metadata() -> Option<&'static gst::subclass::ElementMetadata> {
|
|
|
|
static ELEMENT_METADATA: Lazy<gst::subclass::ElementMetadata> = Lazy::new(|| {
|
2022-08-25 22:30:08 +00:00
|
|
|
#[cfg(feature = "doc")]
|
|
|
|
Status::static_type().mark_as_plugin_api(gst::PluginAPIFlags::empty());
|
2021-01-21 18:21:29 +00:00
|
|
|
gst::subclass::ElementMetadata::new(
|
|
|
|
"Fallback Source",
|
|
|
|
"Generic/Source",
|
2022-08-19 14:34:17 +00:00
|
|
|
"Live source with uridecodebin3 or custom source, and fallback stream",
|
2021-01-21 18:21:29 +00:00
|
|
|
"Sebastian Dröge <sebastian@centricular.com>",
|
|
|
|
)
|
|
|
|
});
|
|
|
|
|
|
|
|
Some(&*ELEMENT_METADATA)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn pad_templates() -> &'static [gst::PadTemplate] {
|
|
|
|
static PAD_TEMPLATES: Lazy<Vec<gst::PadTemplate>> = Lazy::new(|| {
|
|
|
|
let audio_src_pad_template = gst::PadTemplate::new(
|
|
|
|
"audio",
|
|
|
|
gst::PadDirection::Src,
|
|
|
|
gst::PadPresence::Sometimes,
|
|
|
|
&gst::Caps::new_any(),
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
let video_src_pad_template = gst::PadTemplate::new(
|
|
|
|
"video",
|
|
|
|
gst::PadDirection::Src,
|
|
|
|
gst::PadPresence::Sometimes,
|
|
|
|
&gst::Caps::new_any(),
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
vec![audio_src_pad_template, video_src_pad_template]
|
|
|
|
});
|
|
|
|
|
|
|
|
PAD_TEMPLATES.as_ref()
|
|
|
|
}
|
|
|
|
|
2020-04-08 12:11:16 +00:00
|
|
|
#[allow(clippy::single_match)]
|
|
|
|
fn change_state(
|
|
|
|
&self,
|
|
|
|
transition: gst::StateChange,
|
|
|
|
) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
|
2022-10-09 13:06:59 +00:00
|
|
|
gst::debug!(CAT, imp: self, "Changing state {:?}", transition);
|
2022-08-19 14:34:17 +00:00
|
|
|
|
2020-04-08 12:11:16 +00:00
|
|
|
match transition {
|
|
|
|
gst::StateChange::NullToReady => {
|
2022-10-09 13:06:59 +00:00
|
|
|
self.start()?;
|
2020-04-08 12:11:16 +00:00
|
|
|
}
|
|
|
|
_ => (),
|
|
|
|
}
|
|
|
|
|
2022-10-09 13:06:59 +00:00
|
|
|
self.parent_change_state(transition).map_err(|err| {
|
|
|
|
gst::error!(
|
|
|
|
CAT,
|
|
|
|
imp: self,
|
|
|
|
"Parent state change transition {:?} failed",
|
|
|
|
transition
|
|
|
|
);
|
2022-08-19 14:34:17 +00:00
|
|
|
|
2022-10-09 13:06:59 +00:00
|
|
|
err
|
|
|
|
})?;
|
2020-04-08 12:11:16 +00:00
|
|
|
|
|
|
|
// Change the source state manually here to be able to catch errors. State changes always
|
|
|
|
// happen from sink to source, so we do this after chaining up.
|
2022-10-09 13:06:59 +00:00
|
|
|
self.change_source_state(transition, false);
|
2022-08-19 14:34:17 +00:00
|
|
|
|
|
|
|
// Change the fallback source state manually here to be able to catch errors. State changes always
|
|
|
|
// happen from sink to source, so we do this after chaining up.
|
2022-10-09 13:06:59 +00:00
|
|
|
self.change_source_state(transition, true);
|
2020-04-08 12:11:16 +00:00
|
|
|
|
|
|
|
// Ignore parent state change return to prevent spurious async/no-preroll return values
|
|
|
|
// due to core state change bugs
|
|
|
|
match transition {
|
|
|
|
gst::StateChange::ReadyToPaused | gst::StateChange::PlayingToPaused => {
|
|
|
|
Ok(gst::StateChangeSuccess::NoPreroll)
|
|
|
|
}
|
|
|
|
gst::StateChange::ReadyToNull => {
|
2022-10-09 13:06:59 +00:00
|
|
|
self.stop();
|
2020-04-08 12:11:16 +00:00
|
|
|
Ok(gst::StateChangeSuccess::Success)
|
|
|
|
}
|
|
|
|
_ => Ok(gst::StateChangeSuccess::Success),
|
|
|
|
}
|
|
|
|
}
|
2021-05-27 01:23:31 +00:00
|
|
|
|
2022-10-09 13:06:59 +00:00
|
|
|
fn send_event(&self, event: gst::Event) -> bool {
|
2021-05-27 01:23:31 +00:00
|
|
|
match event.view() {
|
|
|
|
gst::EventView::Eos(..) => {
|
2022-08-19 14:34:17 +00:00
|
|
|
gst::debug!(
|
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: self,
|
2022-08-19 14:34:17 +00:00
|
|
|
"Handling element-level EOS, forwarding to all streams"
|
|
|
|
);
|
2021-05-27 01:23:31 +00:00
|
|
|
|
2021-09-13 15:08:45 +00:00
|
|
|
let mut state_guard = self.state.lock();
|
2021-05-27 01:23:31 +00:00
|
|
|
let state = match &mut *state_guard {
|
|
|
|
None => {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
Some(state) => state,
|
|
|
|
};
|
|
|
|
|
|
|
|
// We don't want to hold the state lock while pushing out EOS
|
2022-08-19 14:34:17 +00:00
|
|
|
let mut send_eos_elements = vec![];
|
|
|
|
let mut send_eos_pads = vec![];
|
2021-05-27 01:23:31 +00:00
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
send_eos_elements.push(state.source.source.clone());
|
|
|
|
|
|
|
|
// Not strictly necessary as the switch will EOS when receiving
|
|
|
|
// EOS on its primary pad, just good form.
|
|
|
|
if let Some(ref source) = state.fallback_source {
|
|
|
|
send_eos_elements.push(source.source.clone());
|
|
|
|
}
|
|
|
|
if let Some(ref source) = state.audio_dummy_source {
|
|
|
|
send_eos_elements.push(source.clone());
|
|
|
|
}
|
|
|
|
if let Some(ref source) = state.video_dummy_source {
|
|
|
|
send_eos_elements.push(source.clone());
|
|
|
|
}
|
2021-05-27 01:23:31 +00:00
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
for branch in [&mut state.video_stream, &mut state.audio_stream]
|
2021-05-27 01:23:31 +00:00
|
|
|
.iter_mut()
|
|
|
|
.filter_map(|v| v.as_mut())
|
2022-08-19 14:34:17 +00:00
|
|
|
.flat_map(|s| [s.main_branch.as_mut(), s.fallback_branch.as_mut()])
|
|
|
|
.flatten()
|
2021-05-27 01:23:31 +00:00
|
|
|
{
|
|
|
|
// If our source hadn't been connected to the switch as a primary
|
|
|
|
// stream, we need to send EOS there ourselves
|
2022-08-19 14:34:17 +00:00
|
|
|
let queue_sinkpad = branch.queue.static_pad("sink").unwrap();
|
|
|
|
send_eos_pads.push(queue_sinkpad.clone());
|
2021-05-27 01:23:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
drop(state_guard);
|
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
for elem in send_eos_elements {
|
2021-05-27 01:23:31 +00:00
|
|
|
elem.send_event(event.clone());
|
|
|
|
}
|
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
for pad in send_eos_pads {
|
2021-05-27 01:23:31 +00:00
|
|
|
pad.send_event(event.clone());
|
|
|
|
}
|
|
|
|
|
|
|
|
true
|
|
|
|
}
|
|
|
|
_ => true,
|
|
|
|
}
|
|
|
|
}
|
2020-04-08 12:11:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl BinImpl for FallbackSrc {
|
2022-10-09 13:06:59 +00:00
|
|
|
fn handle_message(&self, msg: gst::Message) {
|
2020-04-08 12:11:16 +00:00
|
|
|
use gst::MessageView;
|
|
|
|
|
|
|
|
match msg.view() {
|
2022-01-19 13:07:45 +00:00
|
|
|
MessageView::Buffering(m) => {
|
2020-04-08 12:11:16 +00:00
|
|
|
// Don't forward upwards, we handle this internally
|
2022-10-09 13:06:59 +00:00
|
|
|
self.handle_buffering(m);
|
2020-04-08 12:11:16 +00:00
|
|
|
}
|
2022-01-19 13:07:45 +00:00
|
|
|
MessageView::StreamsSelected(m) => {
|
2020-04-08 12:11:16 +00:00
|
|
|
// Don't forward upwards, we are exposing streams based on properties
|
|
|
|
// TODO: Do stream configuration via our own stream collection and handling
|
|
|
|
// of stream select events
|
2020-05-19 13:51:59 +00:00
|
|
|
// TODO: Also needs updating of StreamCollection handling in CustomSource
|
2022-10-09 13:06:59 +00:00
|
|
|
self.handle_streams_selected(m);
|
2020-04-08 12:11:16 +00:00
|
|
|
}
|
2022-01-19 13:07:45 +00:00
|
|
|
MessageView::Error(m) => {
|
2022-10-09 13:06:59 +00:00
|
|
|
if !self.handle_error(m) {
|
|
|
|
self.parent_handle_message(msg);
|
2020-04-08 12:11:16 +00:00
|
|
|
}
|
|
|
|
}
|
2022-10-09 13:06:59 +00:00
|
|
|
_ => self.parent_handle_message(msg),
|
2020-04-08 12:11:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl FallbackSrc {
|
2022-08-19 14:34:17 +00:00
|
|
|
fn create_dummy_audio_source(filter_caps: &gst::Caps, min_latency: gst::ClockTime) -> gst::Bin {
|
2022-10-22 16:06:29 +00:00
|
|
|
let bin = gst::Bin::default();
|
2022-08-19 14:34:17 +00:00
|
|
|
|
2022-10-19 16:18:43 +00:00
|
|
|
let audiotestsrc = gst::ElementFactory::make("audiotestsrc")
|
|
|
|
.name("audiosrc")
|
|
|
|
.property_from_str("wave", "silence")
|
|
|
|
.property("is-live", true)
|
|
|
|
.build()
|
2022-08-19 14:34:17 +00:00
|
|
|
.expect("No audiotestsrc found");
|
|
|
|
|
2022-10-19 16:18:43 +00:00
|
|
|
let audioconvert = gst::ElementFactory::make("audioconvert")
|
|
|
|
.name("audio_audioconvert")
|
|
|
|
.build()
|
2022-08-19 14:34:17 +00:00
|
|
|
.expect("No audioconvert found");
|
|
|
|
|
2022-10-19 16:18:43 +00:00
|
|
|
let audioresample = gst::ElementFactory::make("audioresample")
|
|
|
|
.name("audio_audioresample")
|
|
|
|
.build()
|
2022-08-19 14:34:17 +00:00
|
|
|
.expect("No audioresample found");
|
|
|
|
|
2022-10-19 16:18:43 +00:00
|
|
|
let capsfilter = gst::ElementFactory::make("capsfilter")
|
|
|
|
.name("audio_capsfilter")
|
|
|
|
.property("caps", filter_caps)
|
|
|
|
.build()
|
2022-08-19 14:34:17 +00:00
|
|
|
.expect("No capsfilter found");
|
|
|
|
|
2022-10-19 16:18:43 +00:00
|
|
|
let queue = gst::ElementFactory::make("queue")
|
|
|
|
.property("max-size-bytes", 0u32)
|
|
|
|
.property("max-size-buffers", 0u32)
|
|
|
|
.property("max-size-time", cmp::max(min_latency, 1.seconds()))
|
|
|
|
.build()
|
|
|
|
.expect("No queue found");
|
2022-08-19 14:34:17 +00:00
|
|
|
|
2023-03-09 14:46:52 +00:00
|
|
|
bin.add_many([
|
2022-08-19 14:34:17 +00:00
|
|
|
&audiotestsrc,
|
|
|
|
&audioconvert,
|
|
|
|
&audioresample,
|
|
|
|
&capsfilter,
|
|
|
|
&queue,
|
|
|
|
])
|
|
|
|
.unwrap();
|
|
|
|
|
2023-03-09 14:46:52 +00:00
|
|
|
gst::Element::link_many([
|
2022-08-19 14:34:17 +00:00
|
|
|
&audiotestsrc,
|
|
|
|
&audioconvert,
|
|
|
|
&audioresample,
|
|
|
|
&capsfilter,
|
|
|
|
&queue,
|
|
|
|
])
|
|
|
|
.unwrap();
|
|
|
|
|
2023-05-10 15:02:08 +00:00
|
|
|
let ghostpad = gst::GhostPad::with_target(&queue.static_pad("src").unwrap()).unwrap();
|
2022-08-19 14:34:17 +00:00
|
|
|
ghostpad.set_active(true).unwrap();
|
|
|
|
bin.add_pad(&ghostpad).unwrap();
|
|
|
|
|
|
|
|
bin
|
|
|
|
}
|
|
|
|
|
|
|
|
fn create_dummy_video_source(filter_caps: &gst::Caps, min_latency: gst::ClockTime) -> gst::Bin {
|
2022-10-22 16:06:29 +00:00
|
|
|
let bin = gst::Bin::default();
|
2022-08-19 14:34:17 +00:00
|
|
|
|
2022-10-19 16:18:43 +00:00
|
|
|
let videotestsrc = gst::ElementFactory::make("videotestsrc")
|
|
|
|
.name("videosrc")
|
|
|
|
.property_from_str("pattern", "black")
|
|
|
|
.property("is-live", true)
|
|
|
|
.build()
|
2022-08-19 14:34:17 +00:00
|
|
|
.expect("No videotestsrc found");
|
|
|
|
|
2022-10-19 16:18:43 +00:00
|
|
|
let videoconvert = gst::ElementFactory::make("videoconvert")
|
|
|
|
.name("video_videoconvert")
|
|
|
|
.build()
|
2022-08-19 14:34:17 +00:00
|
|
|
.expect("No videoconvert found");
|
|
|
|
|
2022-10-19 16:18:43 +00:00
|
|
|
let videoscale = gst::ElementFactory::make("videoscale")
|
|
|
|
.name("video_videoscale")
|
|
|
|
.build()
|
2022-08-19 14:34:17 +00:00
|
|
|
.expect("No videoscale found");
|
|
|
|
|
2022-10-19 16:18:43 +00:00
|
|
|
let capsfilter = gst::ElementFactory::make("capsfilter")
|
|
|
|
.name("video_capsfilter")
|
|
|
|
.property("caps", filter_caps)
|
|
|
|
.build()
|
2022-08-19 14:34:17 +00:00
|
|
|
.expect("No capsfilter found");
|
|
|
|
|
2022-10-19 16:18:43 +00:00
|
|
|
let queue = gst::ElementFactory::make("queue")
|
|
|
|
.property("max-size-bytes", 0u32)
|
|
|
|
.property("max-size-buffers", 0u32)
|
|
|
|
.property("max-size-time", cmp::max(min_latency, 1.seconds()))
|
|
|
|
.build()
|
|
|
|
.expect("No queue found");
|
2022-08-19 14:34:17 +00:00
|
|
|
|
2023-03-09 14:46:52 +00:00
|
|
|
bin.add_many([
|
2022-08-19 14:34:17 +00:00
|
|
|
&videotestsrc,
|
|
|
|
&videoconvert,
|
|
|
|
&videoscale,
|
|
|
|
&capsfilter,
|
|
|
|
&queue,
|
|
|
|
])
|
|
|
|
.unwrap();
|
|
|
|
|
2023-03-09 14:46:52 +00:00
|
|
|
gst::Element::link_many([
|
2022-08-19 14:34:17 +00:00
|
|
|
&videotestsrc,
|
|
|
|
&videoconvert,
|
|
|
|
&videoscale,
|
|
|
|
&capsfilter,
|
|
|
|
&queue,
|
|
|
|
])
|
|
|
|
.unwrap();
|
|
|
|
|
2023-05-10 15:02:08 +00:00
|
|
|
let ghostpad = gst::GhostPad::with_target(&queue.static_pad("src").unwrap()).unwrap();
|
2022-08-19 14:34:17 +00:00
|
|
|
ghostpad.set_active(true).unwrap();
|
|
|
|
bin.add_pad(&ghostpad).unwrap();
|
|
|
|
|
|
|
|
bin
|
|
|
|
}
|
|
|
|
|
2022-10-09 13:06:59 +00:00
|
|
|
fn create_main_input(&self, source: &Source, buffer_duration: i64) -> SourceBin {
|
2022-10-22 16:06:29 +00:00
|
|
|
let bin = gst::Bin::default();
|
2022-08-19 14:34:17 +00:00
|
|
|
|
2020-05-19 13:51:59 +00:00
|
|
|
let source = match source {
|
|
|
|
Source::Uri(ref uri) => {
|
2022-10-09 13:06:59 +00:00
|
|
|
let uri = self
|
2022-10-23 20:03:22 +00:00
|
|
|
.obj()
|
2022-10-09 13:06:59 +00:00
|
|
|
.emit_by_name::<glib::GString>("update-uri", &[uri]);
|
2020-09-22 12:06:07 +00:00
|
|
|
|
2022-10-19 16:18:43 +00:00
|
|
|
let source = gst::ElementFactory::make("uridecodebin3")
|
|
|
|
.name("uridecodebin")
|
|
|
|
.property("uri", uri)
|
|
|
|
.property("use-buffering", true)
|
|
|
|
.property("buffer-duration", buffer_duration)
|
|
|
|
.build()
|
|
|
|
.expect("No uridecodebin3 found");
|
2020-05-19 13:51:59 +00:00
|
|
|
|
|
|
|
source
|
|
|
|
}
|
2020-11-15 12:08:54 +00:00
|
|
|
Source::Element(ref source) => CustomSource::new(source).upcast(),
|
2020-05-19 13:51:59 +00:00
|
|
|
};
|
2020-04-08 12:11:16 +00:00
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
bin.add(&source).unwrap();
|
|
|
|
|
2020-04-08 12:11:16 +00:00
|
|
|
// Handle any async state changes internally, they don't affect the pipeline because we
|
|
|
|
// convert everything to a live stream
|
2022-08-19 14:34:17 +00:00
|
|
|
bin.set_property("async-handling", true);
|
2020-04-08 12:11:16 +00:00
|
|
|
// Don't let the bin handle state changes of the source. We want to do it manually to catch
|
|
|
|
// possible errors and retry, without causing the whole bin state change to fail
|
2022-08-19 14:34:17 +00:00
|
|
|
bin.set_locked_state(true);
|
2020-04-08 12:11:16 +00:00
|
|
|
|
2022-10-09 13:06:59 +00:00
|
|
|
source.connect_pad_added(move |source, pad| {
|
|
|
|
let element = match source
|
|
|
|
.parent()
|
|
|
|
.and_then(|p| p.parent())
|
|
|
|
.and_then(|p| p.downcast::<super::FallbackSrc>().ok())
|
|
|
|
{
|
2020-04-08 12:11:16 +00:00
|
|
|
None => return,
|
|
|
|
Some(element) => element,
|
|
|
|
};
|
2022-10-09 13:06:59 +00:00
|
|
|
let imp = element.imp();
|
2020-04-08 12:11:16 +00:00
|
|
|
|
2022-10-09 13:06:59 +00:00
|
|
|
if let Err(msg) = imp.handle_source_pad_added(pad, false) {
|
2020-06-30 20:57:22 +00:00
|
|
|
element.post_error_message(msg);
|
2020-04-08 12:11:16 +00:00
|
|
|
}
|
|
|
|
});
|
2022-10-09 13:06:59 +00:00
|
|
|
source.connect_pad_removed(move |source, pad| {
|
|
|
|
let element = match source
|
|
|
|
.parent()
|
|
|
|
.and_then(|p| p.parent())
|
|
|
|
.and_then(|p| p.downcast::<super::FallbackSrc>().ok())
|
|
|
|
{
|
2020-04-08 12:11:16 +00:00
|
|
|
None => return,
|
|
|
|
Some(element) => element,
|
|
|
|
};
|
2022-10-09 13:06:59 +00:00
|
|
|
let imp = element.imp();
|
2020-04-08 12:11:16 +00:00
|
|
|
|
2022-10-09 13:06:59 +00:00
|
|
|
imp.handle_source_pad_removed(pad, false);
|
2020-04-08 12:11:16 +00:00
|
|
|
});
|
|
|
|
|
2022-10-23 20:03:22 +00:00
|
|
|
self.obj().add(&bin).unwrap();
|
2020-04-08 12:11:16 +00:00
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
SourceBin {
|
|
|
|
source: bin,
|
|
|
|
pending_restart: false,
|
|
|
|
is_live: false,
|
|
|
|
is_image: false,
|
|
|
|
restart_timeout: None,
|
|
|
|
pending_restart_timeout: None,
|
|
|
|
retry_timeout: None,
|
|
|
|
streams: None,
|
|
|
|
}
|
2020-04-08 12:11:16 +00:00
|
|
|
}
|
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
fn create_fallback_input(
|
2020-04-08 12:11:16 +00:00
|
|
|
&self,
|
|
|
|
fallback_uri: Option<&str>,
|
2022-08-19 14:34:17 +00:00
|
|
|
buffer_duration: i64,
|
|
|
|
) -> Option<SourceBin> {
|
|
|
|
let source: gst::Element = match fallback_uri {
|
|
|
|
Some(uri) => {
|
2022-10-19 16:18:43 +00:00
|
|
|
let dbin = gst::ElementFactory::make("uridecodebin3")
|
|
|
|
.name("uridecodebin")
|
|
|
|
.property("uri", uri)
|
|
|
|
.property("use-buffering", true)
|
|
|
|
.property("buffer-duration", buffer_duration)
|
|
|
|
.build()
|
2022-08-19 14:34:17 +00:00
|
|
|
.expect("No uridecodebin3 found");
|
2020-04-08 12:11:16 +00:00
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
dbin
|
|
|
|
}
|
|
|
|
None => return None,
|
|
|
|
};
|
2020-04-08 12:11:16 +00:00
|
|
|
|
2022-10-22 16:06:29 +00:00
|
|
|
let bin = gst::Bin::default();
|
2020-04-08 12:11:16 +00:00
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
bin.add(&source).unwrap();
|
|
|
|
|
2022-10-09 13:06:59 +00:00
|
|
|
source.connect_pad_added(move |source, pad| {
|
|
|
|
let element = match source
|
|
|
|
.parent()
|
|
|
|
.and_then(|p| p.parent())
|
|
|
|
.and_then(|p| p.downcast::<super::FallbackSrc>().ok())
|
|
|
|
{
|
2022-08-19 14:34:17 +00:00
|
|
|
None => return,
|
|
|
|
Some(element) => element,
|
|
|
|
};
|
2022-10-09 13:06:59 +00:00
|
|
|
let imp = element.imp();
|
2022-08-19 14:34:17 +00:00
|
|
|
|
2022-10-09 13:06:59 +00:00
|
|
|
if let Err(msg) = imp.handle_source_pad_added(pad, true) {
|
2022-08-19 14:34:17 +00:00
|
|
|
element.post_error_message(msg);
|
|
|
|
}
|
|
|
|
});
|
2022-10-09 13:06:59 +00:00
|
|
|
source.connect_pad_removed(move |source, pad| {
|
|
|
|
let element = match source
|
|
|
|
.parent()
|
|
|
|
.and_then(|p| p.parent())
|
|
|
|
.and_then(|p| p.downcast::<super::FallbackSrc>().ok())
|
|
|
|
{
|
2022-08-19 14:34:17 +00:00
|
|
|
None => return,
|
|
|
|
Some(element) => element,
|
|
|
|
};
|
2022-10-23 20:03:22 +00:00
|
|
|
let src = element.imp();
|
2022-10-09 13:06:59 +00:00
|
|
|
src.handle_source_pad_removed(pad, true);
|
2022-08-19 14:34:17 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
// Handle any async state changes internally, they don't affect the pipeline because we
|
|
|
|
// convert everything to a live stream
|
|
|
|
bin.set_property("async-handling", true);
|
|
|
|
// Don't let the bin handle state changes of the dbin. We want to do it manually to catch
|
|
|
|
// possible errors and retry, without causing the whole bin state change to fail
|
|
|
|
bin.set_locked_state(true);
|
|
|
|
|
2022-10-23 20:03:22 +00:00
|
|
|
self.obj().add(&bin).unwrap();
|
2020-04-08 12:11:16 +00:00
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
Some(SourceBin {
|
|
|
|
source: bin,
|
|
|
|
pending_restart: false,
|
|
|
|
is_live: false,
|
|
|
|
is_image: false,
|
|
|
|
restart_timeout: None,
|
|
|
|
pending_restart_timeout: None,
|
|
|
|
retry_timeout: None,
|
|
|
|
streams: None,
|
|
|
|
})
|
2020-04-08 12:11:16 +00:00
|
|
|
}
|
|
|
|
|
2022-05-25 17:46:46 +00:00
|
|
|
#[allow(clippy::too_many_arguments)]
|
2020-04-08 12:11:16 +00:00
|
|
|
fn create_stream(
|
|
|
|
&self,
|
2021-05-28 16:35:28 +00:00
|
|
|
timeout: gst::ClockTime,
|
|
|
|
min_latency: gst::ClockTime,
|
2020-04-08 12:11:16 +00:00
|
|
|
is_audio: bool,
|
2021-05-21 20:28:06 +00:00
|
|
|
immediate_fallback: bool,
|
2022-08-19 14:34:17 +00:00
|
|
|
dummy_source: &gst::Bin,
|
|
|
|
filter_caps: &gst::Caps,
|
2021-02-09 16:57:34 +00:00
|
|
|
) -> Stream {
|
2022-10-19 16:18:43 +00:00
|
|
|
let switch = gst::ElementFactory::make("fallbackswitch")
|
|
|
|
.property("timeout", timeout.nseconds())
|
|
|
|
.property("min-upstream-latency", min_latency.nseconds())
|
|
|
|
.property("immediate-fallback", immediate_fallback)
|
|
|
|
.build()
|
|
|
|
.expect("No fallbackswitch found");
|
2020-07-30 11:05:08 +00:00
|
|
|
|
2022-10-23 20:03:22 +00:00
|
|
|
self.obj().add(&switch).unwrap();
|
2020-04-08 12:11:16 +00:00
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
let dummy_srcpad = dummy_source.static_pad("src").unwrap();
|
|
|
|
let dummy_sinkpad = switch.request_pad_simple("sink_%u").unwrap();
|
|
|
|
dummy_sinkpad.set_property("priority", 2u32);
|
2023-04-04 09:34:27 +00:00
|
|
|
dummy_srcpad
|
|
|
|
.link_full(
|
|
|
|
&dummy_sinkpad,
|
|
|
|
gst::PadLinkCheck::all() & !gst::PadLinkCheck::CAPS,
|
|
|
|
)
|
|
|
|
.unwrap();
|
2022-04-08 17:24:03 +00:00
|
|
|
|
2022-10-09 13:06:59 +00:00
|
|
|
switch.connect_notify(Some("active-pad"), move |switch, _pspec| {
|
|
|
|
let element = match switch
|
|
|
|
.parent()
|
|
|
|
.and_then(|p| p.downcast::<super::FallbackSrc>().ok())
|
|
|
|
{
|
2022-04-08 16:53:38 +00:00
|
|
|
None => return,
|
|
|
|
Some(element) => element,
|
|
|
|
};
|
|
|
|
|
2022-10-09 13:06:59 +00:00
|
|
|
let imp = element.imp();
|
|
|
|
imp.handle_switch_active_pad_change(is_audio);
|
2022-04-08 16:53:38 +00:00
|
|
|
});
|
|
|
|
|
2021-04-20 12:58:11 +00:00
|
|
|
let srcpad = switch.static_pad("src").unwrap();
|
2022-10-09 13:06:59 +00:00
|
|
|
let templ = self
|
2022-10-23 20:03:22 +00:00
|
|
|
.obj()
|
2021-04-20 12:58:11 +00:00
|
|
|
.pad_template(if is_audio { "audio" } else { "video" })
|
2020-04-08 12:11:16 +00:00
|
|
|
.unwrap();
|
2023-05-10 15:02:08 +00:00
|
|
|
let ghostpad = gst::GhostPad::builder_from_template_with_target(&templ, &srcpad)
|
|
|
|
.unwrap()
|
|
|
|
.name(templ.name())
|
2020-06-23 07:01:27 +00:00
|
|
|
.proxy_pad_chain_function({
|
2022-10-09 13:06:59 +00:00
|
|
|
move |pad, parent, buffer| {
|
|
|
|
let parent = parent.and_then(|p| p.parent());
|
|
|
|
FallbackSrc::catch_panic_pad_function(
|
|
|
|
parent.as_ref(),
|
|
|
|
|| Err(gst::FlowError::Error),
|
|
|
|
|imp| imp.proxy_pad_chain(pad, buffer),
|
|
|
|
)
|
2020-06-23 07:01:27 +00:00
|
|
|
}
|
|
|
|
})
|
2023-05-10 15:02:08 +00:00
|
|
|
.build();
|
2020-04-08 12:11:16 +00:00
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
let _ = ghostpad.set_active(true);
|
|
|
|
|
2022-10-23 20:03:22 +00:00
|
|
|
self.obj().add_pad(&ghostpad).unwrap();
|
2020-04-08 12:11:16 +00:00
|
|
|
|
2021-02-09 16:57:34 +00:00
|
|
|
Stream {
|
2022-08-19 14:34:17 +00:00
|
|
|
main_branch: None,
|
|
|
|
fallback_branch: None,
|
2020-04-08 12:11:16 +00:00
|
|
|
switch,
|
|
|
|
srcpad: ghostpad.upcast(),
|
2022-08-19 14:34:17 +00:00
|
|
|
filter_caps: filter_caps.clone(),
|
2021-02-09 16:57:34 +00:00
|
|
|
}
|
2020-04-08 12:11:16 +00:00
|
|
|
}
|
|
|
|
|
2022-10-09 13:06:59 +00:00
|
|
|
fn start(&self) -> Result<(), gst::StateChangeError> {
|
|
|
|
gst::debug!(CAT, imp: self, "Starting");
|
2021-09-13 15:08:45 +00:00
|
|
|
let mut state_guard = self.state.lock();
|
2020-04-08 12:11:16 +00:00
|
|
|
if state_guard.is_some() {
|
|
|
|
return Err(gst::StateChangeError);
|
|
|
|
}
|
|
|
|
|
2021-09-13 15:08:45 +00:00
|
|
|
let settings = self.settings.lock().clone();
|
2020-05-19 13:51:59 +00:00
|
|
|
let configured_source = match settings
|
|
|
|
.uri
|
|
|
|
.as_ref()
|
|
|
|
.cloned()
|
|
|
|
.map(Source::Uri)
|
|
|
|
.or_else(|| settings.source.as_ref().cloned().map(Source::Element))
|
|
|
|
{
|
|
|
|
Some(source) => source,
|
2020-04-08 12:11:16 +00:00
|
|
|
None => {
|
2022-10-09 13:06:59 +00:00
|
|
|
gst::error!(CAT, imp: self, "No URI or source element configured");
|
|
|
|
gst::element_imp_error!(
|
|
|
|
self,
|
2020-05-19 13:51:59 +00:00
|
|
|
gst::LibraryError::Settings,
|
|
|
|
["No URI or source element configured"]
|
|
|
|
);
|
2020-04-08 12:11:16 +00:00
|
|
|
return Err(gst::StateChangeError);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
let fallback_uri = &settings.fallback_uri;
|
|
|
|
|
|
|
|
// Create main input
|
2022-10-09 13:06:59 +00:00
|
|
|
let source = self.create_main_input(&configured_source, settings.buffer_duration);
|
2020-04-08 12:11:16 +00:00
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
// Create fallback input
|
|
|
|
let fallback_source =
|
2022-10-09 13:06:59 +00:00
|
|
|
self.create_fallback_input(fallback_uri.as_deref(), settings.buffer_duration);
|
2022-08-19 14:34:17 +00:00
|
|
|
|
2020-04-08 12:11:16 +00:00
|
|
|
let mut flow_combiner = gst_base::UniqueFlowCombiner::new();
|
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
// Create video stream and video dummy input
|
|
|
|
let (video_stream, video_dummy_source) = if settings.enable_video {
|
|
|
|
let video_dummy_source = Self::create_dummy_video_source(
|
|
|
|
&settings.fallback_video_caps,
|
|
|
|
settings.min_latency,
|
|
|
|
);
|
2022-10-23 20:03:22 +00:00
|
|
|
self.obj().add(&video_dummy_source).unwrap();
|
2022-08-19 14:34:17 +00:00
|
|
|
|
2020-07-08 10:40:03 +00:00
|
|
|
let stream = self.create_stream(
|
|
|
|
settings.timeout,
|
|
|
|
settings.min_latency,
|
|
|
|
false,
|
2021-05-21 20:28:06 +00:00
|
|
|
settings.immediate_fallback,
|
2022-08-19 14:34:17 +00:00
|
|
|
&video_dummy_source,
|
2022-05-25 17:46:46 +00:00
|
|
|
&settings.fallback_video_caps,
|
2021-02-09 16:57:34 +00:00
|
|
|
);
|
2020-04-08 12:11:16 +00:00
|
|
|
flow_combiner.add_pad(&stream.srcpad);
|
2022-08-19 14:34:17 +00:00
|
|
|
|
|
|
|
(Some(stream), Some(video_dummy_source))
|
2020-04-08 12:11:16 +00:00
|
|
|
} else {
|
2022-08-19 14:34:17 +00:00
|
|
|
(None, None)
|
2020-04-08 12:11:16 +00:00
|
|
|
};
|
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
// Create audio stream and out dummy input
|
|
|
|
let (audio_stream, audio_dummy_source) = if settings.enable_audio {
|
|
|
|
let audio_dummy_source = Self::create_dummy_audio_source(
|
|
|
|
&settings.fallback_audio_caps,
|
|
|
|
settings.min_latency,
|
|
|
|
);
|
2022-10-23 20:03:22 +00:00
|
|
|
self.obj().add(&audio_dummy_source).unwrap();
|
2022-08-19 14:34:17 +00:00
|
|
|
|
2021-05-21 20:28:06 +00:00
|
|
|
let stream = self.create_stream(
|
|
|
|
settings.timeout,
|
|
|
|
settings.min_latency,
|
|
|
|
true,
|
|
|
|
settings.immediate_fallback,
|
2022-08-19 14:34:17 +00:00
|
|
|
&audio_dummy_source,
|
2022-05-25 17:46:46 +00:00
|
|
|
&settings.fallback_audio_caps,
|
2021-05-21 20:28:06 +00:00
|
|
|
);
|
2020-04-08 12:11:16 +00:00
|
|
|
flow_combiner.add_pad(&stream.srcpad);
|
2022-08-19 14:34:17 +00:00
|
|
|
|
|
|
|
(Some(stream), Some(audio_dummy_source))
|
2020-04-08 12:11:16 +00:00
|
|
|
} else {
|
2022-08-19 14:34:17 +00:00
|
|
|
(None, None)
|
2020-04-08 12:11:16 +00:00
|
|
|
};
|
|
|
|
|
2021-05-27 23:43:50 +00:00
|
|
|
let manually_blocked = settings.manual_unblock;
|
|
|
|
|
2020-04-08 12:11:16 +00:00
|
|
|
*state_guard = Some(State {
|
|
|
|
source,
|
2022-08-19 14:34:17 +00:00
|
|
|
fallback_source,
|
2020-04-08 12:11:16 +00:00
|
|
|
video_stream,
|
|
|
|
audio_stream,
|
2022-08-19 14:34:17 +00:00
|
|
|
audio_dummy_source,
|
|
|
|
video_dummy_source,
|
2020-04-08 12:11:16 +00:00
|
|
|
flow_combiner,
|
|
|
|
last_buffering_update: None,
|
2022-08-19 14:34:17 +00:00
|
|
|
fallback_last_buffering_update: None,
|
2020-04-08 12:11:16 +00:00
|
|
|
settings,
|
2020-05-19 13:51:59 +00:00
|
|
|
configured_source,
|
2020-10-29 13:39:58 +00:00
|
|
|
stats: Stats::default(),
|
2021-05-27 23:43:50 +00:00
|
|
|
manually_blocked,
|
|
|
|
schedule_restart_on_unblock: false,
|
2020-04-08 12:11:16 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
drop(state_guard);
|
|
|
|
|
2022-10-23 20:03:22 +00:00
|
|
|
self.obj().no_more_pads();
|
2020-04-08 12:11:16 +00:00
|
|
|
|
2022-10-23 20:03:22 +00:00
|
|
|
self.obj().notify("status");
|
2020-04-08 12:11:16 +00:00
|
|
|
|
2022-10-09 13:06:59 +00:00
|
|
|
gst::debug!(CAT, imp: self, "Started");
|
2020-04-08 12:11:16 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2022-10-09 13:06:59 +00:00
|
|
|
fn stop(&self) {
|
|
|
|
gst::debug!(CAT, imp: self, "Stopping");
|
2021-09-13 15:08:45 +00:00
|
|
|
let mut state_guard = self.state.lock();
|
2020-04-08 12:11:16 +00:00
|
|
|
let mut state = match state_guard.take() {
|
|
|
|
Some(state) => state,
|
2021-02-09 16:57:34 +00:00
|
|
|
None => return,
|
2020-04-08 12:11:16 +00:00
|
|
|
};
|
|
|
|
drop(state_guard);
|
|
|
|
|
2022-10-23 20:03:22 +00:00
|
|
|
self.obj().notify("status");
|
2020-04-08 12:11:16 +00:00
|
|
|
|
2020-05-19 13:51:59 +00:00
|
|
|
// In theory all streams should've been removed from the source's pad-removed signal
|
2020-04-08 12:11:16 +00:00
|
|
|
// handler when going from Paused to Ready but better safe than sorry here
|
|
|
|
for stream in [&state.video_stream, &state.audio_stream]
|
|
|
|
.iter()
|
|
|
|
.filter_map(|v| v.as_ref())
|
|
|
|
{
|
2022-10-23 20:03:22 +00:00
|
|
|
let element = self.obj();
|
2022-10-09 13:06:59 +00:00
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
for branch in [&stream.main_branch, &stream.fallback_branch]
|
|
|
|
.iter()
|
|
|
|
.filter_map(|v| v.as_ref())
|
|
|
|
{
|
|
|
|
element.remove(&branch.queue).unwrap();
|
|
|
|
element.remove(&branch.converters).unwrap();
|
|
|
|
element.remove(&branch.clocksync).unwrap();
|
|
|
|
if branch.switch_pad.parent().as_ref() == Some(stream.switch.upcast_ref()) {
|
|
|
|
stream.switch.release_request_pad(&branch.switch_pad);
|
|
|
|
}
|
|
|
|
}
|
2020-04-08 12:11:16 +00:00
|
|
|
element.remove(&stream.switch).unwrap();
|
2020-07-02 08:27:57 +00:00
|
|
|
let _ = stream.srcpad.set_target(None::<&gst::Pad>);
|
|
|
|
let _ = element.remove_pad(&stream.srcpad);
|
2020-04-08 12:11:16 +00:00
|
|
|
}
|
|
|
|
state.video_stream = None;
|
|
|
|
state.audio_stream = None;
|
|
|
|
|
2020-09-23 16:21:38 +00:00
|
|
|
if let Source::Element(ref source) = state.configured_source {
|
|
|
|
// Explicitly remove the source element from the CustomSource so that we can
|
|
|
|
// later create a new CustomSource and add it again there.
|
2022-08-19 14:34:17 +00:00
|
|
|
if source.has_as_parent(&state.source.source) {
|
2020-09-23 16:21:38 +00:00
|
|
|
let _ = source.set_state(gst::State::Null);
|
|
|
|
let _ = state
|
2022-08-19 14:34:17 +00:00
|
|
|
.source
|
2020-09-23 16:21:38 +00:00
|
|
|
.source
|
|
|
|
.downcast_ref::<gst::Bin>()
|
|
|
|
.unwrap()
|
|
|
|
.remove(source);
|
|
|
|
}
|
|
|
|
}
|
2020-04-08 12:11:16 +00:00
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
for source in [Some(&mut state.source), state.fallback_source.as_mut()]
|
|
|
|
.iter_mut()
|
|
|
|
.flatten()
|
|
|
|
{
|
2022-10-23 20:03:22 +00:00
|
|
|
self.obj().remove(&source.source).unwrap();
|
2020-04-08 12:11:16 +00:00
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
if let Some(timeout) = source.pending_restart_timeout.take() {
|
|
|
|
timeout.unschedule();
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(timeout) = source.retry_timeout.take() {
|
|
|
|
timeout.unschedule();
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(timeout) = source.restart_timeout.take() {
|
|
|
|
timeout.unschedule();
|
|
|
|
}
|
2020-04-08 12:11:16 +00:00
|
|
|
}
|
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
for source in [
|
|
|
|
state.video_dummy_source.take(),
|
|
|
|
state.audio_dummy_source.take(),
|
|
|
|
]
|
|
|
|
.iter()
|
|
|
|
.flatten()
|
|
|
|
{
|
|
|
|
let _ = source.set_state(gst::State::Null);
|
2022-10-23 20:03:22 +00:00
|
|
|
self.obj().remove(source).unwrap();
|
2020-05-21 07:20:16 +00:00
|
|
|
}
|
|
|
|
|
2022-10-09 13:06:59 +00:00
|
|
|
gst::debug!(CAT, imp: self, "Stopped");
|
2020-04-08 12:11:16 +00:00
|
|
|
}
|
|
|
|
|
2022-10-09 13:06:59 +00:00
|
|
|
fn change_source_state(&self, transition: gst::StateChange, fallback_source: bool) {
|
2022-08-19 14:34:17 +00:00
|
|
|
gst::debug!(
|
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: self,
|
2022-08-19 14:34:17 +00:00
|
|
|
"Changing {}source state: {:?}",
|
|
|
|
if fallback_source { "fallback " } else { "" },
|
|
|
|
transition
|
|
|
|
);
|
2021-09-13 15:08:45 +00:00
|
|
|
let mut state_guard = self.state.lock();
|
2020-04-08 12:11:16 +00:00
|
|
|
let state = match &mut *state_guard {
|
|
|
|
Some(state) => state,
|
2021-02-09 16:57:34 +00:00
|
|
|
None => return,
|
2020-04-08 12:11:16 +00:00
|
|
|
};
|
|
|
|
|
2023-07-11 07:09:35 +00:00
|
|
|
let source = if fallback_source {
|
2022-08-19 14:34:17 +00:00
|
|
|
if let Some(ref mut source) = state.fallback_source {
|
|
|
|
source
|
|
|
|
} else {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
&mut state.source
|
|
|
|
};
|
|
|
|
|
|
|
|
if transition.current() <= transition.next() && source.pending_restart {
|
2022-02-21 17:43:46 +00:00
|
|
|
gst::debug!(
|
2020-04-08 12:11:16 +00:00
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: self,
|
2022-08-19 14:34:17 +00:00
|
|
|
"Not starting {}source because pending restart",
|
|
|
|
if fallback_source { "fallback " } else { "" }
|
2020-04-08 12:11:16 +00:00
|
|
|
);
|
2021-02-09 16:57:34 +00:00
|
|
|
return;
|
2022-08-19 14:34:17 +00:00
|
|
|
} else if transition.next() <= gst::State::Ready && source.pending_restart {
|
2022-02-21 17:43:46 +00:00
|
|
|
gst::debug!(
|
2020-04-08 12:11:16 +00:00
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: self,
|
2022-08-19 14:34:17 +00:00
|
|
|
"Unsetting pending {}restart because shutting down",
|
|
|
|
if fallback_source { "fallback " } else { "" }
|
2020-04-08 12:11:16 +00:00
|
|
|
);
|
2022-08-19 14:34:17 +00:00
|
|
|
source.pending_restart = false;
|
|
|
|
if let Some(timeout) = source.pending_restart_timeout.take() {
|
2020-04-08 12:11:16 +00:00
|
|
|
timeout.unschedule();
|
|
|
|
}
|
|
|
|
}
|
2022-08-19 14:34:17 +00:00
|
|
|
let source = source.source.clone();
|
2020-04-08 12:11:16 +00:00
|
|
|
drop(state_guard);
|
|
|
|
|
2022-10-23 20:03:22 +00:00
|
|
|
self.obj().notify("status");
|
2020-04-08 12:11:16 +00:00
|
|
|
|
|
|
|
let res = source.set_state(transition.next());
|
|
|
|
match res {
|
|
|
|
Err(_) => {
|
2022-08-19 14:34:17 +00:00
|
|
|
gst::error!(
|
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: self,
|
2022-08-19 14:34:17 +00:00
|
|
|
"{}source failed to change state",
|
|
|
|
if fallback_source { "fallback " } else { "" }
|
|
|
|
);
|
2020-04-08 12:11:16 +00:00
|
|
|
// Try again later if we're not shutting down
|
|
|
|
if transition != gst::StateChange::ReadyToNull {
|
2020-05-08 10:55:21 +00:00
|
|
|
let _ = source.set_state(gst::State::Null);
|
2021-09-13 15:08:45 +00:00
|
|
|
let mut state_guard = self.state.lock();
|
2020-04-08 12:11:16 +00:00
|
|
|
let state = state_guard.as_mut().expect("no state");
|
2022-08-19 14:34:17 +00:00
|
|
|
self.handle_source_error(
|
|
|
|
state,
|
|
|
|
RetryReason::StateChangeFailure,
|
|
|
|
fallback_source,
|
|
|
|
);
|
2020-10-29 13:39:58 +00:00
|
|
|
drop(state_guard);
|
2022-10-23 20:03:22 +00:00
|
|
|
self.obj().notify("statistics");
|
2020-04-08 12:11:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(res) => {
|
2022-02-21 17:43:46 +00:00
|
|
|
gst::debug!(
|
2020-04-08 12:11:16 +00:00
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: self,
|
2022-08-19 14:34:17 +00:00
|
|
|
"{}source changed state successfully: {:?}",
|
|
|
|
if fallback_source { "fallback " } else { "" },
|
2020-04-08 12:11:16 +00:00
|
|
|
res
|
|
|
|
);
|
2020-07-02 09:23:48 +00:00
|
|
|
|
2021-09-13 15:08:45 +00:00
|
|
|
let mut state_guard = self.state.lock();
|
2020-07-02 09:23:48 +00:00
|
|
|
let state = state_guard.as_mut().expect("no state");
|
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
let source = if fallback_source {
|
|
|
|
if let Some(ref mut source) = state.fallback_source {
|
|
|
|
source
|
|
|
|
} else {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
&mut state.source
|
|
|
|
};
|
|
|
|
|
2020-04-08 12:11:16 +00:00
|
|
|
// Remember if the source is live
|
|
|
|
if transition == gst::StateChange::ReadyToPaused {
|
2022-08-19 14:34:17 +00:00
|
|
|
source.is_live = res == gst::StateChangeSuccess::NoPreroll;
|
2020-04-08 12:11:16 +00:00
|
|
|
}
|
2020-07-02 09:23:48 +00:00
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
if (!source.is_live && transition == gst::StateChange::ReadyToPaused)
|
|
|
|
|| (source.is_live && transition == gst::StateChange::PausedToPlaying)
|
|
|
|
{
|
|
|
|
if !fallback_source {
|
|
|
|
state.schedule_restart_on_unblock = true;
|
|
|
|
}
|
|
|
|
if source.restart_timeout.is_none() {
|
|
|
|
self.schedule_source_restart_timeout(
|
|
|
|
state,
|
|
|
|
gst::ClockTime::ZERO,
|
|
|
|
fallback_source,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
} else if (!source.is_live && transition == gst::StateChange::PausedToReady)
|
|
|
|
|| (source.is_live && transition == gst::StateChange::PlayingToPaused)
|
2020-07-02 09:23:48 +00:00
|
|
|
{
|
2022-08-19 14:34:17 +00:00
|
|
|
if let Some(timeout) = source.pending_restart_timeout.take() {
|
|
|
|
timeout.unschedule();
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(timeout) = source.retry_timeout.take() {
|
|
|
|
timeout.unschedule();
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(timeout) = source.restart_timeout.take() {
|
|
|
|
timeout.unschedule();
|
|
|
|
}
|
2020-07-02 09:23:48 +00:00
|
|
|
}
|
2020-04-08 12:11:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn proxy_pad_chain(
|
|
|
|
&self,
|
|
|
|
pad: &gst::ProxyPad,
|
|
|
|
buffer: gst::Buffer,
|
|
|
|
) -> Result<gst::FlowSuccess, gst::FlowError> {
|
2022-10-23 20:03:22 +00:00
|
|
|
let res = gst::ProxyPad::chain_default(pad, Some(&*self.obj()), buffer);
|
2020-04-08 12:11:16 +00:00
|
|
|
|
2021-09-13 15:08:45 +00:00
|
|
|
let mut state_guard = self.state.lock();
|
2020-04-08 12:11:16 +00:00
|
|
|
let state = match &mut *state_guard {
|
|
|
|
None => return res,
|
|
|
|
Some(state) => state,
|
|
|
|
};
|
|
|
|
|
|
|
|
state.flow_combiner.update_pad_flow(pad, res)
|
|
|
|
}
|
|
|
|
|
2022-10-20 13:19:28 +00:00
|
|
|
fn create_image_converts(
|
|
|
|
&self,
|
|
|
|
filter_caps: &gst::Caps,
|
|
|
|
fallback_source: bool,
|
|
|
|
) -> gst::Element {
|
|
|
|
let imagefreeze = gst::ElementFactory::make("imagefreeze")
|
|
|
|
.property("is-live", true)
|
|
|
|
.build()
|
|
|
|
.expect("No imagefreeze found");
|
|
|
|
|
|
|
|
if !fallback_source || filter_caps.is_any() {
|
|
|
|
return imagefreeze;
|
|
|
|
}
|
|
|
|
|
2022-10-22 16:06:29 +00:00
|
|
|
let bin = gst::Bin::default();
|
2022-10-20 13:19:28 +00:00
|
|
|
let videoconvert = gst::ElementFactory::make("videoconvert")
|
|
|
|
.name("video_videoconvert")
|
|
|
|
.build()
|
|
|
|
.expect("No videoconvert found");
|
|
|
|
|
|
|
|
let videoscale = gst::ElementFactory::make("videoscale")
|
|
|
|
.name("video_videoscale")
|
|
|
|
.build()
|
|
|
|
.expect("No videoscale found");
|
|
|
|
|
|
|
|
let capsfilter = gst::ElementFactory::make("capsfilter")
|
|
|
|
.name("video_capsfilter")
|
|
|
|
.property("caps", filter_caps)
|
|
|
|
.build()
|
|
|
|
.expect("No capsfilter found");
|
|
|
|
|
2023-03-09 14:46:52 +00:00
|
|
|
bin.add_many([&videoconvert, &videoscale, &imagefreeze, &capsfilter])
|
2022-10-20 13:19:28 +00:00
|
|
|
.unwrap();
|
|
|
|
|
2023-03-09 14:46:52 +00:00
|
|
|
gst::Element::link_many([&videoconvert, &videoscale, &imagefreeze, &capsfilter]).unwrap();
|
2022-10-20 13:19:28 +00:00
|
|
|
|
|
|
|
let ghostpad =
|
2023-05-10 15:02:08 +00:00
|
|
|
gst::GhostPad::with_target(&videoconvert.static_pad("sink").unwrap()).unwrap();
|
2022-10-20 13:19:28 +00:00
|
|
|
ghostpad.set_active(true).unwrap();
|
|
|
|
bin.add_pad(&ghostpad).unwrap();
|
|
|
|
|
2023-05-10 15:02:08 +00:00
|
|
|
let ghostpad = gst::GhostPad::with_target(&capsfilter.static_pad("src").unwrap()).unwrap();
|
2022-10-20 13:19:28 +00:00
|
|
|
ghostpad.set_active(true).unwrap();
|
|
|
|
bin.add_pad(&ghostpad).unwrap();
|
|
|
|
|
|
|
|
bin.upcast()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn create_video_converts(
|
|
|
|
&self,
|
|
|
|
filter_caps: &gst::Caps,
|
|
|
|
fallback_source: bool,
|
|
|
|
) -> gst::Element {
|
|
|
|
if !fallback_source || filter_caps.is_any() {
|
|
|
|
return gst::ElementFactory::make("identity")
|
|
|
|
.build()
|
|
|
|
.expect("No identity found");
|
|
|
|
}
|
|
|
|
|
2022-10-22 16:06:29 +00:00
|
|
|
let bin = gst::Bin::default();
|
2022-10-20 13:19:28 +00:00
|
|
|
let videoconvert = gst::ElementFactory::make("videoconvert")
|
|
|
|
.name("video_videoconvert")
|
|
|
|
.build()
|
|
|
|
.expect("No videoconvert found");
|
|
|
|
|
|
|
|
let videoscale = gst::ElementFactory::make("videoscale")
|
|
|
|
.name("video_videoscale")
|
|
|
|
.build()
|
|
|
|
.expect("No videoscale found");
|
|
|
|
|
|
|
|
let capsfilter = gst::ElementFactory::make("capsfilter")
|
|
|
|
.name("video_capsfilter")
|
|
|
|
.property("caps", filter_caps)
|
|
|
|
.build()
|
|
|
|
.expect("No capsfilter found");
|
|
|
|
|
2023-03-09 14:46:52 +00:00
|
|
|
bin.add_many([&videoconvert, &videoscale, &capsfilter])
|
2022-10-20 13:19:28 +00:00
|
|
|
.unwrap();
|
|
|
|
|
2023-03-09 14:46:52 +00:00
|
|
|
gst::Element::link_many([&videoconvert, &videoscale, &capsfilter]).unwrap();
|
2022-10-20 13:19:28 +00:00
|
|
|
|
|
|
|
let ghostpad =
|
2023-05-10 15:02:08 +00:00
|
|
|
gst::GhostPad::with_target(&videoconvert.static_pad("sink").unwrap()).unwrap();
|
2022-10-20 13:19:28 +00:00
|
|
|
ghostpad.set_active(true).unwrap();
|
|
|
|
bin.add_pad(&ghostpad).unwrap();
|
|
|
|
|
2023-05-10 15:02:08 +00:00
|
|
|
let ghostpad = gst::GhostPad::with_target(&capsfilter.static_pad("src").unwrap()).unwrap();
|
2022-10-20 13:19:28 +00:00
|
|
|
ghostpad.set_active(true).unwrap();
|
|
|
|
bin.add_pad(&ghostpad).unwrap();
|
|
|
|
|
|
|
|
bin.upcast()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn create_audio_converts(
|
|
|
|
&self,
|
|
|
|
filter_caps: &gst::Caps,
|
|
|
|
fallback_source: bool,
|
|
|
|
) -> gst::Element {
|
2023-05-17 14:49:09 +00:00
|
|
|
if !fallback_source || filter_caps.is_any() {
|
2022-10-20 13:19:28 +00:00
|
|
|
return gst::ElementFactory::make("identity")
|
|
|
|
.build()
|
|
|
|
.expect("No identity found");
|
|
|
|
}
|
|
|
|
|
2022-10-22 16:06:29 +00:00
|
|
|
let bin = gst::Bin::default();
|
2022-10-20 13:19:28 +00:00
|
|
|
let audioconvert = gst::ElementFactory::make("audioconvert")
|
|
|
|
.name("audio_audioconvert")
|
|
|
|
.build()
|
|
|
|
.expect("No audioconvert found");
|
|
|
|
|
|
|
|
let audioresample = gst::ElementFactory::make("audioresample")
|
|
|
|
.name("audio_audioresample")
|
|
|
|
.build()
|
|
|
|
.expect("No audioresample found");
|
|
|
|
|
|
|
|
let capsfilter = gst::ElementFactory::make("capsfilter")
|
|
|
|
.name("audio_capsfilter")
|
|
|
|
.property("caps", filter_caps)
|
|
|
|
.build()
|
|
|
|
.expect("No capsfilter found");
|
|
|
|
|
2023-03-09 14:46:52 +00:00
|
|
|
bin.add_many([&audioconvert, &audioresample, &capsfilter])
|
2022-10-20 13:19:28 +00:00
|
|
|
.unwrap();
|
|
|
|
|
2023-03-09 14:46:52 +00:00
|
|
|
gst::Element::link_many([&audioconvert, &audioresample, &capsfilter]).unwrap();
|
2022-10-20 13:19:28 +00:00
|
|
|
|
|
|
|
let ghostpad =
|
2023-05-10 15:02:08 +00:00
|
|
|
gst::GhostPad::with_target(&audioconvert.static_pad("sink").unwrap()).unwrap();
|
2022-10-20 13:19:28 +00:00
|
|
|
ghostpad.set_active(true).unwrap();
|
|
|
|
bin.add_pad(&ghostpad).unwrap();
|
|
|
|
|
2023-05-10 15:02:08 +00:00
|
|
|
let ghostpad = gst::GhostPad::with_target(&capsfilter.static_pad("src").unwrap()).unwrap();
|
2022-10-20 13:19:28 +00:00
|
|
|
ghostpad.set_active(true).unwrap();
|
|
|
|
bin.add_pad(&ghostpad).unwrap();
|
|
|
|
|
|
|
|
bin.upcast()
|
|
|
|
}
|
|
|
|
|
2020-05-19 13:51:59 +00:00
|
|
|
fn handle_source_pad_added(
|
2020-04-08 12:11:16 +00:00
|
|
|
&self,
|
|
|
|
pad: &gst::Pad,
|
2022-08-19 14:34:17 +00:00
|
|
|
fallback_source: bool,
|
2020-04-08 12:11:16 +00:00
|
|
|
) -> Result<(), gst::ErrorMessage> {
|
2022-08-19 14:34:17 +00:00
|
|
|
gst::debug!(
|
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: self,
|
2022-08-19 14:34:17 +00:00
|
|
|
"Pad {} added to {}source",
|
|
|
|
pad.name(),
|
|
|
|
if fallback_source { "fallback " } else { "" }
|
|
|
|
);
|
|
|
|
|
|
|
|
let mut is_image = false;
|
|
|
|
|
|
|
|
if let Some(ev) = pad.sticky_event::<gst::event::StreamStart>(0) {
|
|
|
|
let stream = ev.stream();
|
|
|
|
|
|
|
|
if let Some(caps) = stream.and_then(|s| s.caps()) {
|
|
|
|
if let Some(s) = caps.structure(0) {
|
|
|
|
is_image = s.name().starts_with("image/");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-04-08 12:11:16 +00:00
|
|
|
|
2021-09-13 15:08:45 +00:00
|
|
|
let mut state_guard = self.state.lock();
|
2020-04-08 12:11:16 +00:00
|
|
|
let state = match &mut *state_guard {
|
|
|
|
None => {
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
Some(state) => state,
|
|
|
|
};
|
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
let source = if fallback_source {
|
|
|
|
if let Some(ref mut source) = state.fallback_source {
|
|
|
|
source
|
|
|
|
} else {
|
|
|
|
return Ok(());
|
2021-06-03 23:29:09 +00:00
|
|
|
}
|
2022-08-19 14:34:17 +00:00
|
|
|
} else {
|
|
|
|
&mut state.source
|
|
|
|
};
|
2021-06-03 23:29:09 +00:00
|
|
|
|
|
|
|
if is_image {
|
2022-08-19 14:34:17 +00:00
|
|
|
if let Some(timeout) = source.pending_restart_timeout.take() {
|
2021-06-03 23:29:09 +00:00
|
|
|
timeout.unschedule();
|
|
|
|
}
|
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
if let Some(timeout) = source.retry_timeout.take() {
|
2021-06-03 23:29:09 +00:00
|
|
|
timeout.unschedule();
|
|
|
|
}
|
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
if let Some(timeout) = source.restart_timeout.take() {
|
2021-06-03 23:29:09 +00:00
|
|
|
timeout.unschedule();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
source.is_image |= is_image;
|
2021-06-03 23:29:09 +00:00
|
|
|
|
2021-05-25 22:16:31 +00:00
|
|
|
let (is_video, stream) = match pad.name() {
|
2022-08-19 14:34:17 +00:00
|
|
|
x if x.starts_with("audio") => (false, &mut state.audio_stream),
|
|
|
|
x if x.starts_with("video") => (true, &mut state.video_stream),
|
2020-04-08 12:11:16 +00:00
|
|
|
_ => {
|
2021-04-12 12:49:54 +00:00
|
|
|
let caps = match pad.current_caps().unwrap_or_else(|| pad.query_caps(None)) {
|
2020-12-05 18:29:50 +00:00
|
|
|
caps if !caps.is_any() && !caps.is_empty() => caps,
|
2020-05-19 13:51:59 +00:00
|
|
|
_ => return Ok(()),
|
|
|
|
};
|
|
|
|
|
2021-04-20 12:58:11 +00:00
|
|
|
let s = caps.structure(0).unwrap();
|
2020-05-19 13:51:59 +00:00
|
|
|
|
2021-04-12 12:49:54 +00:00
|
|
|
if s.name().starts_with("audio/") {
|
2021-05-25 22:16:31 +00:00
|
|
|
(false, &mut state.audio_stream)
|
2021-04-12 12:49:54 +00:00
|
|
|
} else if s.name().starts_with("video/") {
|
2021-05-25 22:16:31 +00:00
|
|
|
(true, &mut state.video_stream)
|
2020-05-19 13:51:59 +00:00
|
|
|
} else {
|
|
|
|
// TODO: handle subtitles etc
|
|
|
|
return Ok(());
|
|
|
|
}
|
2020-04-08 12:11:16 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2021-05-25 22:16:31 +00:00
|
|
|
let type_ = if is_video { "video" } else { "audio" };
|
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
let (branch_storage, filter_caps, switch) = match stream {
|
2020-04-08 12:11:16 +00:00
|
|
|
None => {
|
2022-10-09 13:06:59 +00:00
|
|
|
gst::debug!(CAT, imp: self, "No {} stream enabled", type_);
|
2020-04-08 12:11:16 +00:00
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
Some(Stream {
|
2022-08-19 14:34:17 +00:00
|
|
|
ref mut main_branch,
|
|
|
|
ref switch,
|
|
|
|
ref filter_caps,
|
|
|
|
..
|
|
|
|
}) if !fallback_source => {
|
|
|
|
if main_branch.is_some() {
|
2022-10-09 13:06:59 +00:00
|
|
|
gst::debug!(CAT, imp: self, "Already configured a {} stream", type_);
|
2022-08-19 14:34:17 +00:00
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
|
|
|
|
(main_branch, filter_caps, switch)
|
|
|
|
}
|
|
|
|
Some(Stream {
|
|
|
|
ref mut fallback_branch,
|
|
|
|
ref switch,
|
|
|
|
ref filter_caps,
|
2020-04-08 12:11:16 +00:00
|
|
|
..
|
|
|
|
}) => {
|
2022-08-19 14:34:17 +00:00
|
|
|
if fallback_branch.is_some() {
|
|
|
|
gst::debug!(
|
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: self,
|
2022-08-19 14:34:17 +00:00
|
|
|
"Already configured a {} fallback stream",
|
|
|
|
type_
|
|
|
|
);
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
|
|
|
|
(fallback_branch, filter_caps, switch)
|
2020-04-08 12:11:16 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-10-20 13:19:28 +00:00
|
|
|
// Configure conversion elements only for fallback stream
|
|
|
|
// (if fallback caps is not ANY) or image source.
|
|
|
|
let converters = if is_image {
|
|
|
|
self.create_image_converts(filter_caps, fallback_source)
|
|
|
|
} else if is_video {
|
|
|
|
self.create_video_converts(filter_caps, fallback_source)
|
2022-08-19 14:34:17 +00:00
|
|
|
} else {
|
2022-10-20 13:19:28 +00:00
|
|
|
self.create_audio_converts(filter_caps, fallback_source)
|
2022-08-19 14:34:17 +00:00
|
|
|
};
|
|
|
|
|
2022-10-19 16:18:43 +00:00
|
|
|
let queue = gst::ElementFactory::make("queue")
|
|
|
|
.property("max-size-bytes", 0u32)
|
|
|
|
.property("max-size-buffers", 0u32)
|
|
|
|
.property(
|
2022-08-19 14:34:17 +00:00
|
|
|
"max-size-time",
|
2022-10-19 16:18:43 +00:00
|
|
|
cmp::max(state.settings.min_latency, 1.seconds()),
|
|
|
|
)
|
|
|
|
.build()
|
|
|
|
.unwrap();
|
|
|
|
let clocksync = gst::ElementFactory::make("clocksync")
|
|
|
|
.build()
|
|
|
|
.unwrap_or_else(|_| {
|
|
|
|
let identity = gst::ElementFactory::make("identity")
|
|
|
|
.property("sync", true)
|
|
|
|
.build()
|
|
|
|
.unwrap();
|
|
|
|
identity
|
|
|
|
});
|
2022-08-19 14:34:17 +00:00
|
|
|
|
|
|
|
source
|
|
|
|
.source
|
2023-03-09 14:46:52 +00:00
|
|
|
.add_many([&converters, &queue, &clocksync])
|
2022-08-19 14:34:17 +00:00
|
|
|
.unwrap();
|
|
|
|
converters.sync_state_with_parent().unwrap();
|
|
|
|
queue.sync_state_with_parent().unwrap();
|
|
|
|
clocksync.sync_state_with_parent().unwrap();
|
|
|
|
|
|
|
|
let sinkpad = converters.static_pad("sink").unwrap();
|
|
|
|
pad.link(&sinkpad).map_err(|err| {
|
2022-10-09 13:06:59 +00:00
|
|
|
gst::error!(CAT, imp: self, "Failed to link new source pad: {}", err);
|
2020-12-20 18:43:45 +00:00
|
|
|
gst::error_msg!(
|
2020-04-08 12:11:16 +00:00
|
|
|
gst::CoreError::Negotiation,
|
2022-08-19 14:34:17 +00:00
|
|
|
["Failed to link new source pad: {}", err]
|
2020-04-08 12:11:16 +00:00
|
|
|
)
|
|
|
|
})?;
|
|
|
|
|
2023-03-09 14:46:52 +00:00
|
|
|
gst::Element::link_many([&converters, &queue, &clocksync]).unwrap();
|
2022-08-19 14:34:17 +00:00
|
|
|
|
2023-05-10 15:02:08 +00:00
|
|
|
let ghostpad = gst::GhostPad::builder_with_target(&clocksync.static_pad("src").unwrap())
|
|
|
|
.unwrap()
|
|
|
|
.name(type_)
|
|
|
|
.build();
|
2022-08-19 14:34:17 +00:00
|
|
|
let _ = ghostpad.set_active(true);
|
|
|
|
source.source.add_pad(&ghostpad).unwrap();
|
|
|
|
|
|
|
|
// Link the new source pad in
|
|
|
|
let switch_pad = switch.request_pad_simple("sink_%u").unwrap();
|
2022-11-01 08:27:48 +00:00
|
|
|
switch_pad.set_property("priority", u32::from(fallback_source));
|
2023-04-04 09:34:27 +00:00
|
|
|
ghostpad
|
|
|
|
.link_full(
|
|
|
|
&switch_pad,
|
|
|
|
gst::PadLinkCheck::all() & !gst::PadLinkCheck::CAPS,
|
|
|
|
)
|
|
|
|
.unwrap();
|
2022-08-19 14:34:17 +00:00
|
|
|
|
2021-05-25 22:16:31 +00:00
|
|
|
pad.add_probe(gst::PadProbeType::EVENT_DOWNSTREAM, move |pad, info| {
|
2022-10-09 13:06:59 +00:00
|
|
|
let element = match pad
|
|
|
|
.parent()
|
|
|
|
.and_then(|p| p.parent())
|
|
|
|
.and_then(|p| p.parent())
|
|
|
|
.and_then(|p| p.downcast::<super::FallbackSrc>().ok())
|
|
|
|
{
|
2021-05-25 22:16:31 +00:00
|
|
|
None => return gst::PadProbeReturn::Ok,
|
|
|
|
Some(element) => element,
|
|
|
|
};
|
2020-07-03 12:37:36 +00:00
|
|
|
|
2022-10-09 13:06:59 +00:00
|
|
|
let imp = element.imp();
|
2021-05-25 22:16:31 +00:00
|
|
|
|
2023-10-16 16:16:52 +00:00
|
|
|
let Some(ev) = info.event() else {
|
|
|
|
return gst::PadProbeReturn::Ok;
|
|
|
|
};
|
2021-05-25 22:16:31 +00:00
|
|
|
|
2023-10-16 16:16:52 +00:00
|
|
|
if ev.type_() != gst::EventType::Eos {
|
|
|
|
return gst::PadProbeReturn::Ok;
|
|
|
|
}
|
2021-06-03 23:29:09 +00:00
|
|
|
|
2023-10-16 16:16:52 +00:00
|
|
|
gst::debug!(
|
|
|
|
CAT,
|
|
|
|
obj: element,
|
|
|
|
"Received EOS from {}source on pad {}",
|
|
|
|
if fallback_source { "fallback " } else { "" },
|
|
|
|
pad.name()
|
|
|
|
);
|
2020-07-03 12:37:36 +00:00
|
|
|
|
2023-10-16 16:16:52 +00:00
|
|
|
let mut state_guard = imp.state.lock();
|
|
|
|
let state = match &mut *state_guard {
|
|
|
|
None => {
|
|
|
|
return gst::PadProbeReturn::Ok;
|
|
|
|
}
|
|
|
|
Some(state) => state,
|
|
|
|
};
|
2022-10-05 16:13:36 +00:00
|
|
|
|
2023-10-16 16:16:52 +00:00
|
|
|
if is_image {
|
|
|
|
gst::PadProbeReturn::Ok
|
|
|
|
} else if state.settings.restart_on_eos || fallback_source {
|
|
|
|
imp.handle_source_error(state, RetryReason::Eos, fallback_source);
|
|
|
|
drop(state_guard);
|
|
|
|
element.notify("statistics");
|
2021-05-25 22:16:31 +00:00
|
|
|
|
2023-10-16 16:16:52 +00:00
|
|
|
gst::PadProbeReturn::Drop
|
|
|
|
} else {
|
|
|
|
// Send EOS to all sinkpads of the fallbackswitch and also to the other
|
|
|
|
// stream's fallbackswitch if it doesn't have a main branch.
|
|
|
|
let mut sinkpads = vec![];
|
2022-10-05 16:13:36 +00:00
|
|
|
|
2023-10-16 16:16:52 +00:00
|
|
|
if let Some(stream) = {
|
|
|
|
if is_video {
|
|
|
|
state.video_stream.as_ref()
|
|
|
|
} else {
|
|
|
|
state.audio_stream.as_ref()
|
|
|
|
}
|
|
|
|
} {
|
|
|
|
sinkpads.extend(stream.switch.sink_pads().into_iter().filter(|p| p != pad));
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(other_stream) = {
|
|
|
|
if is_video {
|
|
|
|
state.audio_stream.as_ref()
|
|
|
|
} else {
|
|
|
|
state.video_stream.as_ref()
|
|
|
|
}
|
|
|
|
} {
|
|
|
|
if other_stream.main_branch.is_none() {
|
|
|
|
sinkpads.extend(
|
|
|
|
other_stream
|
|
|
|
.switch
|
|
|
|
.sink_pads()
|
|
|
|
.into_iter()
|
|
|
|
.filter(|p| p != pad),
|
|
|
|
);
|
2020-07-03 12:37:36 +00:00
|
|
|
}
|
|
|
|
}
|
2023-10-16 16:16:52 +00:00
|
|
|
|
|
|
|
let event = ev.clone();
|
|
|
|
element.call_async(move |_| {
|
|
|
|
for sinkpad in sinkpads {
|
|
|
|
sinkpad.send_event(event.clone());
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
gst::PadProbeReturn::Ok
|
2021-05-25 22:16:31 +00:00
|
|
|
}
|
|
|
|
});
|
2020-07-03 12:37:36 +00:00
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
let queue_srcpad = queue.static_pad("src").unwrap();
|
2022-10-09 13:06:59 +00:00
|
|
|
let source_srcpad_block = Some(self.add_pad_probe(pad, &queue_srcpad, fallback_source));
|
2022-08-19 14:34:17 +00:00
|
|
|
|
|
|
|
*branch_storage = Some(StreamBranch {
|
|
|
|
source_srcpad: pad.clone(),
|
|
|
|
source_srcpad_block,
|
|
|
|
clocksync,
|
|
|
|
converters,
|
|
|
|
queue,
|
|
|
|
queue_srcpad,
|
|
|
|
switch_pad,
|
|
|
|
});
|
2020-04-08 12:11:16 +00:00
|
|
|
|
|
|
|
drop(state_guard);
|
2022-10-23 20:03:22 +00:00
|
|
|
self.obj().notify("status");
|
2020-04-08 12:11:16 +00:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2022-10-09 13:06:59 +00:00
|
|
|
fn add_pad_probe(&self, pad: &gst::Pad, block_pad: &gst::Pad, fallback_source: bool) -> Block {
|
2020-07-31 08:18:05 +00:00
|
|
|
// FIXME: Not literally correct as we add the probe to the queue source pad but that's only
|
|
|
|
// a workaround until
|
|
|
|
// https://gitlab.freedesktop.org/gstreamer/gst-plugins-base/-/issues/800
|
|
|
|
// is fixed.
|
2022-02-21 17:43:46 +00:00
|
|
|
gst::debug!(
|
2020-07-31 08:18:05 +00:00
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: self,
|
2022-08-19 14:34:17 +00:00
|
|
|
"Adding blocking probe to pad {} for pad {} (fallback: {})",
|
|
|
|
block_pad.name(),
|
|
|
|
pad.name(),
|
|
|
|
fallback_source,
|
2020-07-31 08:18:05 +00:00
|
|
|
);
|
2020-04-08 12:11:16 +00:00
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
let probe_id = block_pad
|
2020-04-08 12:11:16 +00:00
|
|
|
.add_probe(
|
2020-05-20 09:02:27 +00:00
|
|
|
gst::PadProbeType::BLOCK
|
|
|
|
| gst::PadProbeType::BUFFER
|
|
|
|
| gst::PadProbeType::EVENT_DOWNSTREAM,
|
2020-04-08 12:11:16 +00:00
|
|
|
move |pad, info| {
|
2022-10-09 13:06:59 +00:00
|
|
|
let element = match pad
|
|
|
|
.parent()
|
|
|
|
.and_then(|p| p.parent())
|
|
|
|
.and_then(|p| p.parent())
|
|
|
|
.and_then(|p| p.downcast::<super::FallbackSrc>().ok())
|
|
|
|
{
|
|
|
|
None => return gst::PadProbeReturn::Ok,
|
2020-04-08 12:11:16 +00:00
|
|
|
Some(element) => element,
|
|
|
|
};
|
2022-10-09 13:06:59 +00:00
|
|
|
|
2020-05-20 09:02:27 +00:00
|
|
|
let pts = match info.data {
|
2021-04-12 12:49:54 +00:00
|
|
|
Some(gst::PadProbeData::Buffer(ref buffer)) => buffer.pts(),
|
2020-05-20 09:02:27 +00:00
|
|
|
Some(gst::PadProbeData::Event(ref ev)) => match ev.view() {
|
2022-01-19 13:07:45 +00:00
|
|
|
gst::EventView::Gap(ev) => Some(ev.get().0),
|
2020-05-20 09:02:27 +00:00
|
|
|
_ => return gst::PadProbeReturn::Pass,
|
|
|
|
},
|
2020-04-08 12:11:16 +00:00
|
|
|
_ => unreachable!(),
|
|
|
|
};
|
|
|
|
|
2022-10-09 13:06:59 +00:00
|
|
|
let imp = element.imp();
|
2020-04-08 12:11:16 +00:00
|
|
|
|
2022-10-09 13:06:59 +00:00
|
|
|
if let Err(msg) = imp.handle_pad_blocked(pad, pts, fallback_source) {
|
|
|
|
imp.post_error_message(msg);
|
2020-04-08 12:11:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
gst::PadProbeReturn::Ok
|
|
|
|
},
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
|
2022-10-19 14:03:15 +00:00
|
|
|
let qos_probe_id = block_pad
|
|
|
|
.add_probe(gst::PadProbeType::EVENT_UPSTREAM, |_pad, info| {
|
2023-10-16 16:16:52 +00:00
|
|
|
let Some(ev) = info.event() else {
|
|
|
|
return gst::PadProbeReturn::Ok;
|
|
|
|
};
|
|
|
|
|
|
|
|
if ev.type_() != gst::EventType::Qos {
|
|
|
|
return gst::PadProbeReturn::Ok;
|
2022-10-19 14:03:15 +00:00
|
|
|
}
|
|
|
|
|
2023-10-16 16:16:52 +00:00
|
|
|
gst::PadProbeReturn::Drop
|
2022-10-19 14:03:15 +00:00
|
|
|
})
|
|
|
|
.unwrap();
|
|
|
|
|
2020-04-08 12:11:16 +00:00
|
|
|
Block {
|
2022-08-19 14:34:17 +00:00
|
|
|
pad: block_pad.clone(),
|
2020-04-08 12:11:16 +00:00
|
|
|
probe_id,
|
2022-10-19 14:03:15 +00:00
|
|
|
qos_probe_id,
|
2021-05-28 16:35:28 +00:00
|
|
|
running_time: gst::ClockTime::NONE,
|
2020-04-08 12:11:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-19 13:51:59 +00:00
|
|
|
fn handle_pad_blocked(
|
2020-04-08 12:11:16 +00:00
|
|
|
&self,
|
|
|
|
pad: &gst::Pad,
|
2021-05-28 16:35:28 +00:00
|
|
|
pts: impl Into<Option<gst::ClockTime>>,
|
2022-08-19 14:34:17 +00:00
|
|
|
fallback_source: bool,
|
2020-04-08 12:11:16 +00:00
|
|
|
) -> Result<(), gst::ErrorMessage> {
|
2021-09-13 15:08:45 +00:00
|
|
|
let mut state_guard = self.state.lock();
|
2020-04-08 12:11:16 +00:00
|
|
|
let state = match &mut *state_guard {
|
|
|
|
None => {
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
Some(state) => state,
|
|
|
|
};
|
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
let (branch, source) = match &mut *state {
|
|
|
|
State {
|
|
|
|
audio_stream:
|
|
|
|
Some(Stream {
|
|
|
|
main_branch: Some(ref mut branch),
|
|
|
|
..
|
|
|
|
}),
|
|
|
|
ref source,
|
|
|
|
..
|
|
|
|
} if !fallback_source && &branch.queue_srcpad == pad => {
|
|
|
|
gst::debug!(
|
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: self,
|
2022-08-19 14:34:17 +00:00
|
|
|
"Called probe on pad {} for pad {} (fallback: {})",
|
|
|
|
pad.name(),
|
|
|
|
branch.source_srcpad.name(),
|
|
|
|
fallback_source
|
|
|
|
);
|
2020-07-31 08:18:05 +00:00
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
(branch, source)
|
|
|
|
}
|
|
|
|
State {
|
|
|
|
audio_stream:
|
|
|
|
Some(Stream {
|
|
|
|
fallback_branch: Some(ref mut branch),
|
|
|
|
..
|
|
|
|
}),
|
|
|
|
fallback_source: Some(ref source),
|
|
|
|
..
|
|
|
|
} if fallback_source && &branch.queue_srcpad == pad => {
|
|
|
|
gst::debug!(
|
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: self,
|
2022-08-19 14:34:17 +00:00
|
|
|
"Called probe on pad {} for pad {} (fallback: {})",
|
|
|
|
pad.name(),
|
|
|
|
branch.source_srcpad.name(),
|
|
|
|
fallback_source
|
|
|
|
);
|
|
|
|
|
|
|
|
(branch, source)
|
|
|
|
}
|
|
|
|
State {
|
|
|
|
video_stream:
|
|
|
|
Some(Stream {
|
|
|
|
main_branch: Some(ref mut branch),
|
|
|
|
..
|
|
|
|
}),
|
|
|
|
ref source,
|
|
|
|
..
|
|
|
|
} if !fallback_source && &branch.queue_srcpad == pad => {
|
|
|
|
gst::debug!(
|
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: self,
|
2022-08-19 14:34:17 +00:00
|
|
|
"Called probe on pad {} for pad {} (fallback: {})",
|
|
|
|
pad.name(),
|
|
|
|
branch.source_srcpad.name(),
|
|
|
|
fallback_source,
|
|
|
|
);
|
|
|
|
|
|
|
|
(branch, source)
|
|
|
|
}
|
|
|
|
State {
|
|
|
|
video_stream:
|
|
|
|
Some(Stream {
|
|
|
|
fallback_branch: Some(ref mut branch),
|
|
|
|
..
|
|
|
|
}),
|
|
|
|
fallback_source: Some(ref source),
|
|
|
|
..
|
|
|
|
} if fallback_source && &branch.queue_srcpad == pad => {
|
|
|
|
gst::debug!(
|
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: self,
|
2022-08-19 14:34:17 +00:00
|
|
|
"Called probe on pad {} for pad {} (fallback: {})",
|
|
|
|
pad.name(),
|
|
|
|
branch.source_srcpad.name(),
|
|
|
|
fallback_source
|
|
|
|
);
|
|
|
|
|
|
|
|
(branch, source)
|
|
|
|
}
|
|
|
|
_ => unreachable!(),
|
2020-07-31 08:18:05 +00:00
|
|
|
};
|
|
|
|
|
2020-04-08 12:11:16 +00:00
|
|
|
// Directly unblock for live streams
|
2022-08-19 14:34:17 +00:00
|
|
|
if source.is_live {
|
|
|
|
if let Some(block) = branch.source_srcpad_block.take() {
|
2022-02-21 17:43:46 +00:00
|
|
|
gst::debug!(
|
2020-08-04 07:36:28 +00:00
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: self,
|
2022-08-19 14:34:17 +00:00
|
|
|
"Removing pad probe on pad {} for pad {} (fallback: {})",
|
|
|
|
pad.name(),
|
|
|
|
branch.source_srcpad.name(),
|
|
|
|
fallback_source,
|
2020-08-04 07:36:28 +00:00
|
|
|
);
|
2020-04-08 12:11:16 +00:00
|
|
|
block.pad.remove_probe(block.probe_id);
|
2022-10-19 14:03:15 +00:00
|
|
|
block.pad.remove_probe(block.qos_probe_id);
|
2020-04-08 12:11:16 +00:00
|
|
|
}
|
|
|
|
|
2022-10-09 13:06:59 +00:00
|
|
|
gst::debug!(CAT, imp: self, "Live source, unblocking directly");
|
2020-04-08 12:11:16 +00:00
|
|
|
|
|
|
|
drop(state_guard);
|
2022-10-23 20:03:22 +00:00
|
|
|
self.obj().notify("status");
|
2020-04-08 12:11:16 +00:00
|
|
|
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update running time for this block
|
2022-08-19 14:34:17 +00:00
|
|
|
let block = match branch.source_srcpad_block {
|
2020-04-08 12:11:16 +00:00
|
|
|
Some(ref mut block) => block,
|
|
|
|
None => return Ok(()),
|
|
|
|
};
|
|
|
|
|
2022-01-22 10:18:02 +00:00
|
|
|
let segment = match pad.sticky_event::<gst::event::Segment>(0) {
|
2021-10-17 14:27:40 +00:00
|
|
|
Some(ev) => ev.segment().clone(),
|
2020-11-02 19:58:44 +00:00
|
|
|
None => {
|
2022-10-09 13:06:59 +00:00
|
|
|
gst::warning!(CAT, imp: self, "Have no segment event yet");
|
2020-11-02 19:58:44 +00:00
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2021-10-17 14:27:40 +00:00
|
|
|
let segment = segment.downcast::<gst::ClockTime>().map_err(|_| {
|
2022-10-09 13:06:59 +00:00
|
|
|
gst::error!(CAT, imp: self, "Have no time segment");
|
2020-12-20 18:43:45 +00:00
|
|
|
gst::error_msg!(gst::CoreError::Clock, ["Have no time segment"])
|
2020-04-08 12:11:16 +00:00
|
|
|
})?;
|
|
|
|
|
2021-05-28 16:35:28 +00:00
|
|
|
let pts = pts.into();
|
|
|
|
let running_time = if let Some((_, start)) =
|
|
|
|
pts.zip(segment.start()).filter(|(pts, start)| pts < start)
|
|
|
|
{
|
2021-12-09 10:30:42 +00:00
|
|
|
segment.to_running_time(start)
|
2021-05-28 16:35:28 +00:00
|
|
|
} else if let Some((_, stop)) = pts.zip(segment.stop()).filter(|(pts, stop)| pts >= stop) {
|
2021-12-09 10:30:42 +00:00
|
|
|
segment.to_running_time(stop)
|
2020-04-08 12:11:16 +00:00
|
|
|
} else {
|
|
|
|
segment.to_running_time(pts)
|
|
|
|
};
|
|
|
|
|
2022-02-21 17:43:46 +00:00
|
|
|
gst::debug!(
|
2020-04-08 12:11:16 +00:00
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: self,
|
2020-07-31 08:18:05 +00:00
|
|
|
"Have block running time {}",
|
2021-05-28 16:35:28 +00:00
|
|
|
running_time.display(),
|
2020-04-08 12:11:16 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
block.running_time = running_time;
|
|
|
|
|
2022-10-09 13:06:59 +00:00
|
|
|
self.unblock_pads(state, fallback_source);
|
2020-04-08 12:11:16 +00:00
|
|
|
|
|
|
|
drop(state_guard);
|
2022-10-23 20:03:22 +00:00
|
|
|
self.obj().notify("status");
|
2020-04-08 12:11:16 +00:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2022-10-09 13:06:59 +00:00
|
|
|
fn unblock_pads(&self, state: &mut State, fallback_source: bool) {
|
2022-10-23 20:03:22 +00:00
|
|
|
let current_running_time = match self.obj().current_running_time() {
|
2022-08-19 14:34:17 +00:00
|
|
|
Some(current_running_time) => current_running_time,
|
|
|
|
None => {
|
2022-10-09 13:06:59 +00:00
|
|
|
gst::debug!(CAT, imp: self, "Waiting for current_running_time");
|
2022-08-19 14:34:17 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
if !fallback_source && state.manually_blocked {
|
2022-10-09 13:06:59 +00:00
|
|
|
gst::debug!(CAT, imp: self, "Not unblocking yet: manual unblock",);
|
2021-05-27 23:43:50 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-04-08 12:11:16 +00:00
|
|
|
// Check if all streams are blocked and have a running time and we have
|
|
|
|
// 100% buffering
|
2022-08-19 14:34:17 +00:00
|
|
|
if (fallback_source && state.stats.fallback_buffering_percent < 100)
|
|
|
|
|| (!fallback_source && state.stats.buffering_percent < 100)
|
|
|
|
{
|
2022-02-21 17:43:46 +00:00
|
|
|
gst::debug!(
|
2020-04-08 12:11:16 +00:00
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: self,
|
2020-04-08 12:11:16 +00:00
|
|
|
"Not unblocking yet: buffering {}%",
|
2020-10-29 13:39:58 +00:00
|
|
|
state.stats.buffering_percent
|
2020-04-08 12:11:16 +00:00
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
let source = if fallback_source {
|
|
|
|
if let Some(ref source) = state.fallback_source {
|
|
|
|
source
|
|
|
|
} else {
|
|
|
|
// There are no blocked pads if there is no fallback source
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
&state.source
|
|
|
|
};
|
|
|
|
|
|
|
|
let streams = match source.streams {
|
2020-04-08 12:11:16 +00:00
|
|
|
None => {
|
2022-10-09 13:06:59 +00:00
|
|
|
gst::debug!(CAT, imp: self, "Have no stream collection yet");
|
2020-04-08 12:11:16 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
Some(ref streams) => streams,
|
|
|
|
};
|
|
|
|
let mut have_audio = false;
|
|
|
|
let mut have_video = false;
|
|
|
|
for stream in streams.iter() {
|
2021-04-12 12:49:54 +00:00
|
|
|
have_audio = have_audio || stream.stream_type().contains(gst::StreamType::AUDIO);
|
|
|
|
have_video = have_video || stream.stream_type().contains(gst::StreamType::VIDEO);
|
2020-04-08 12:11:16 +00:00
|
|
|
}
|
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
// For the fallback source, if we have no audio/video then that's OK and we would continue
|
|
|
|
// using the corresponding dummy source
|
|
|
|
let want_audio = if fallback_source {
|
|
|
|
have_audio
|
|
|
|
} else {
|
|
|
|
state.settings.enable_audio
|
|
|
|
};
|
|
|
|
let want_video = if fallback_source {
|
|
|
|
have_video
|
|
|
|
} else {
|
|
|
|
state.settings.enable_video
|
|
|
|
};
|
2020-04-08 12:11:16 +00:00
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
// FIXME: All this surely can be simplified somehow
|
|
|
|
let mut audio_branch = state.audio_stream.as_mut().and_then(|s| {
|
|
|
|
if fallback_source {
|
|
|
|
s.fallback_branch.as_mut()
|
|
|
|
} else {
|
|
|
|
s.main_branch.as_mut()
|
|
|
|
}
|
|
|
|
});
|
|
|
|
let mut video_branch = state.video_stream.as_mut().and_then(|s| {
|
|
|
|
if fallback_source {
|
|
|
|
s.fallback_branch.as_mut()
|
|
|
|
} else {
|
|
|
|
s.main_branch.as_mut()
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
let audio_running_time = audio_branch
|
2020-04-08 12:11:16 +00:00
|
|
|
.as_ref()
|
2022-08-19 14:34:17 +00:00
|
|
|
.and_then(|b| b.source_srcpad_block.as_ref())
|
2021-05-28 16:35:28 +00:00
|
|
|
.and_then(|b| b.running_time);
|
2022-08-19 14:34:17 +00:00
|
|
|
let video_running_time = video_branch
|
2020-04-08 12:11:16 +00:00
|
|
|
.as_ref()
|
2022-08-19 14:34:17 +00:00
|
|
|
.and_then(|b| b.source_srcpad_block.as_ref())
|
2021-05-28 16:35:28 +00:00
|
|
|
.and_then(|b| b.running_time);
|
2020-04-08 12:11:16 +00:00
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
let audio_srcpad = audio_branch.as_ref().map(|b| b.source_srcpad.clone());
|
|
|
|
let video_srcpad = video_branch.as_ref().map(|b| b.source_srcpad.clone());
|
2020-04-08 12:11:16 +00:00
|
|
|
|
|
|
|
let audio_is_eos = audio_srcpad
|
|
|
|
.as_ref()
|
2021-04-12 12:49:54 +00:00
|
|
|
.map(|p| p.pad_flags().contains(gst::PadFlags::EOS))
|
2020-04-08 12:11:16 +00:00
|
|
|
.unwrap_or(false);
|
|
|
|
let video_is_eos = video_srcpad
|
|
|
|
.as_ref()
|
2021-04-12 12:49:54 +00:00
|
|
|
.map(|p| p.pad_flags().contains(gst::PadFlags::EOS))
|
2020-04-08 12:11:16 +00:00
|
|
|
.unwrap_or(false);
|
|
|
|
|
|
|
|
// If we need both, wait for both and take the minimum, otherwise take the one we need.
|
|
|
|
// Also consider EOS, we'd never get a new running time after EOS so don't need to wait.
|
|
|
|
// FIXME: All this surely can be simplified somehow
|
|
|
|
|
|
|
|
if have_audio && want_audio && have_video && want_video {
|
2020-08-04 07:36:28 +00:00
|
|
|
if audio_running_time.is_none()
|
|
|
|
&& !audio_is_eos
|
|
|
|
&& video_running_time.is_none()
|
|
|
|
&& !video_is_eos
|
|
|
|
{
|
2022-10-09 13:06:59 +00:00
|
|
|
gst::debug!(CAT, imp: self, "Waiting for audio and video pads to block");
|
2020-08-04 07:36:28 +00:00
|
|
|
return;
|
|
|
|
} else if audio_running_time.is_none() && !audio_is_eos {
|
2022-10-09 13:06:59 +00:00
|
|
|
gst::debug!(CAT, imp: self, "Waiting for audio pad to block");
|
2020-04-08 12:11:16 +00:00
|
|
|
return;
|
2020-08-04 07:36:28 +00:00
|
|
|
} else if video_running_time.is_none() && !video_is_eos {
|
2022-10-09 13:06:59 +00:00
|
|
|
gst::debug!(CAT, imp: self, "Waiting for video pad to block");
|
2020-04-08 12:11:16 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-05-28 16:35:28 +00:00
|
|
|
let audio_running_time = audio_running_time.expect("checked above");
|
|
|
|
let video_running_time = video_running_time.expect("checked above");
|
|
|
|
|
2020-04-08 12:11:16 +00:00
|
|
|
let min_running_time = if audio_is_eos {
|
|
|
|
video_running_time
|
|
|
|
} else if video_is_eos {
|
|
|
|
audio_running_time
|
|
|
|
} else {
|
2021-05-28 16:35:28 +00:00
|
|
|
audio_running_time.min(video_running_time)
|
2020-04-08 12:11:16 +00:00
|
|
|
};
|
2021-05-28 16:35:28 +00:00
|
|
|
|
2020-04-08 12:11:16 +00:00
|
|
|
let offset = if current_running_time > min_running_time {
|
2021-05-28 16:35:28 +00:00
|
|
|
(current_running_time - min_running_time).nseconds() as i64
|
2020-04-08 12:11:16 +00:00
|
|
|
} else {
|
2021-05-28 16:35:28 +00:00
|
|
|
-((min_running_time - current_running_time).nseconds() as i64)
|
2020-04-08 12:11:16 +00:00
|
|
|
};
|
|
|
|
|
2022-02-21 17:43:46 +00:00
|
|
|
gst::debug!(
|
2020-04-08 12:11:16 +00:00
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: self,
|
2020-04-08 12:11:16 +00:00
|
|
|
"Unblocking at {} with pad offset {} (audio: {} eos {}, video {} eos {})",
|
|
|
|
current_running_time,
|
|
|
|
offset,
|
|
|
|
audio_running_time,
|
|
|
|
audio_is_eos,
|
|
|
|
video_running_time,
|
|
|
|
video_is_eos,
|
|
|
|
);
|
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
if let Some(block) = audio_branch
|
2020-04-08 12:11:16 +00:00
|
|
|
.as_mut()
|
2022-08-19 14:34:17 +00:00
|
|
|
.and_then(|b| b.source_srcpad_block.take())
|
2020-04-08 12:11:16 +00:00
|
|
|
{
|
|
|
|
if !audio_is_eos {
|
|
|
|
block.pad.set_offset(offset);
|
|
|
|
}
|
|
|
|
block.pad.remove_probe(block.probe_id);
|
2022-10-19 14:03:15 +00:00
|
|
|
block.pad.remove_probe(block.qos_probe_id);
|
2020-04-08 12:11:16 +00:00
|
|
|
}
|
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
if let Some(block) = video_branch
|
2020-04-08 12:11:16 +00:00
|
|
|
.as_mut()
|
2022-08-19 14:34:17 +00:00
|
|
|
.and_then(|b| b.source_srcpad_block.take())
|
2020-04-08 12:11:16 +00:00
|
|
|
{
|
|
|
|
if !video_is_eos {
|
|
|
|
block.pad.set_offset(offset);
|
|
|
|
}
|
|
|
|
block.pad.remove_probe(block.probe_id);
|
2022-10-19 14:03:15 +00:00
|
|
|
block.pad.remove_probe(block.qos_probe_id);
|
2020-04-08 12:11:16 +00:00
|
|
|
}
|
|
|
|
} else if have_audio && want_audio {
|
2021-05-28 16:35:28 +00:00
|
|
|
let audio_running_time = match audio_running_time {
|
|
|
|
Some(audio_running_time) => audio_running_time,
|
|
|
|
None => {
|
2022-10-09 13:06:59 +00:00
|
|
|
gst::debug!(CAT, imp: self, "Waiting for audio pad to block");
|
2021-05-28 16:35:28 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
};
|
2020-04-08 12:11:16 +00:00
|
|
|
|
|
|
|
let offset = if current_running_time > audio_running_time {
|
2021-05-28 16:35:28 +00:00
|
|
|
(current_running_time - audio_running_time).nseconds() as i64
|
2020-04-08 12:11:16 +00:00
|
|
|
} else {
|
2021-05-28 16:35:28 +00:00
|
|
|
-((audio_running_time - current_running_time).nseconds() as i64)
|
2020-04-08 12:11:16 +00:00
|
|
|
};
|
|
|
|
|
2022-02-21 17:43:46 +00:00
|
|
|
gst::debug!(
|
2020-04-08 12:11:16 +00:00
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: self,
|
2020-04-08 12:11:16 +00:00
|
|
|
"Unblocking at {} with pad offset {} (audio: {} eos {})",
|
|
|
|
current_running_time,
|
|
|
|
offset,
|
|
|
|
audio_running_time,
|
|
|
|
audio_is_eos
|
|
|
|
);
|
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
if let Some(block) = audio_branch
|
2020-04-08 12:11:16 +00:00
|
|
|
.as_mut()
|
2022-08-19 14:34:17 +00:00
|
|
|
.and_then(|b| b.source_srcpad_block.take())
|
2020-04-08 12:11:16 +00:00
|
|
|
{
|
|
|
|
if !audio_is_eos {
|
|
|
|
block.pad.set_offset(offset);
|
|
|
|
}
|
|
|
|
block.pad.remove_probe(block.probe_id);
|
2022-10-19 14:03:15 +00:00
|
|
|
block.pad.remove_probe(block.qos_probe_id);
|
2020-04-08 12:11:16 +00:00
|
|
|
}
|
|
|
|
} else if have_video && want_video {
|
2021-05-28 16:35:28 +00:00
|
|
|
let video_running_time = match video_running_time {
|
|
|
|
Some(video_running_time) => video_running_time,
|
|
|
|
None => {
|
2022-10-09 13:06:59 +00:00
|
|
|
gst::debug!(CAT, imp: self, "Waiting for video pad to block");
|
2021-05-28 16:35:28 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
};
|
2020-04-08 12:11:16 +00:00
|
|
|
|
|
|
|
let offset = if current_running_time > video_running_time {
|
2021-05-28 16:35:28 +00:00
|
|
|
(current_running_time - video_running_time).nseconds() as i64
|
2020-04-08 12:11:16 +00:00
|
|
|
} else {
|
2021-05-28 16:35:28 +00:00
|
|
|
-((video_running_time - current_running_time).nseconds() as i64)
|
2020-04-08 12:11:16 +00:00
|
|
|
};
|
|
|
|
|
2022-02-21 17:43:46 +00:00
|
|
|
gst::debug!(
|
2020-04-08 12:11:16 +00:00
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: self,
|
2020-04-08 12:11:16 +00:00
|
|
|
"Unblocking at {} with pad offset {} (video: {} eos {})",
|
|
|
|
current_running_time,
|
|
|
|
offset,
|
|
|
|
video_running_time,
|
|
|
|
video_is_eos
|
|
|
|
);
|
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
if let Some(block) = video_branch
|
2020-04-08 12:11:16 +00:00
|
|
|
.as_mut()
|
2022-08-19 14:34:17 +00:00
|
|
|
.and_then(|b| b.source_srcpad_block.take())
|
2020-04-08 12:11:16 +00:00
|
|
|
{
|
|
|
|
if !video_is_eos {
|
|
|
|
block.pad.set_offset(offset);
|
|
|
|
}
|
|
|
|
block.pad.remove_probe(block.probe_id);
|
2022-10-19 14:03:15 +00:00
|
|
|
block.pad.remove_probe(block.qos_probe_id);
|
2020-04-08 12:11:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-09 13:06:59 +00:00
|
|
|
fn handle_source_pad_removed(&self, pad: &gst::Pad, fallback_source: bool) {
|
2022-08-19 14:34:17 +00:00
|
|
|
gst::debug!(
|
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: self,
|
2022-08-19 14:34:17 +00:00
|
|
|
"Pad {} removed from {}source",
|
|
|
|
pad.name(),
|
|
|
|
if fallback_source { "fallback " } else { "" }
|
|
|
|
);
|
2020-04-08 12:11:16 +00:00
|
|
|
|
2021-09-13 15:08:45 +00:00
|
|
|
let mut state_guard = self.state.lock();
|
2020-04-08 12:11:16 +00:00
|
|
|
let state = match &mut *state_guard {
|
|
|
|
None => {
|
2021-02-09 16:57:34 +00:00
|
|
|
return;
|
2020-04-08 12:11:16 +00:00
|
|
|
}
|
|
|
|
Some(state) => state,
|
|
|
|
};
|
|
|
|
|
2022-10-19 16:58:53 +00:00
|
|
|
let (branch, is_video, source, switch) = match &mut *state {
|
2022-08-19 14:34:17 +00:00
|
|
|
State {
|
|
|
|
audio_stream:
|
|
|
|
Some(Stream {
|
|
|
|
ref mut main_branch,
|
|
|
|
ref switch,
|
|
|
|
..
|
|
|
|
}),
|
|
|
|
ref source,
|
|
|
|
..
|
|
|
|
} if !fallback_source
|
|
|
|
&& main_branch.as_ref().map(|b| &b.source_srcpad) == Some(pad) =>
|
|
|
|
{
|
|
|
|
(main_branch.take().unwrap(), false, source, switch)
|
|
|
|
}
|
|
|
|
State {
|
|
|
|
audio_stream:
|
|
|
|
Some(Stream {
|
|
|
|
ref mut fallback_branch,
|
|
|
|
ref switch,
|
|
|
|
..
|
|
|
|
}),
|
|
|
|
fallback_source: Some(ref source),
|
|
|
|
..
|
|
|
|
} if fallback_source
|
|
|
|
&& fallback_branch.as_ref().map(|b| &b.source_srcpad) == Some(pad) =>
|
|
|
|
{
|
|
|
|
(fallback_branch.take().unwrap(), false, source, switch)
|
|
|
|
}
|
|
|
|
State {
|
|
|
|
video_stream:
|
|
|
|
Some(Stream {
|
|
|
|
ref mut main_branch,
|
|
|
|
ref switch,
|
|
|
|
..
|
|
|
|
}),
|
|
|
|
ref source,
|
|
|
|
..
|
|
|
|
} if !fallback_source
|
|
|
|
&& main_branch.as_ref().map(|b| &b.source_srcpad) == Some(pad) =>
|
|
|
|
{
|
|
|
|
(main_branch.take().unwrap(), true, source, switch)
|
|
|
|
}
|
|
|
|
State {
|
|
|
|
video_stream:
|
|
|
|
Some(Stream {
|
|
|
|
ref mut fallback_branch,
|
|
|
|
ref switch,
|
|
|
|
..
|
|
|
|
}),
|
|
|
|
fallback_source: Some(ref source),
|
|
|
|
..
|
|
|
|
} if fallback_source
|
|
|
|
&& fallback_branch.as_ref().map(|b| &b.source_srcpad) == Some(pad) =>
|
|
|
|
{
|
|
|
|
(fallback_branch.take().unwrap(), true, source, switch)
|
|
|
|
}
|
|
|
|
_ => return,
|
2020-04-08 12:11:16 +00:00
|
|
|
};
|
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
branch.queue.set_locked_state(true);
|
|
|
|
let _ = branch.queue.set_state(gst::State::Null);
|
|
|
|
source.source.remove(&branch.queue).unwrap();
|
|
|
|
|
|
|
|
branch.converters.set_locked_state(true);
|
|
|
|
let _ = branch.converters.set_state(gst::State::Null);
|
|
|
|
source.source.remove(&branch.converters).unwrap();
|
2020-04-08 12:11:16 +00:00
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
branch.clocksync.set_locked_state(true);
|
|
|
|
let _ = branch.clocksync.set_state(gst::State::Null);
|
|
|
|
source.source.remove(&branch.clocksync).unwrap();
|
|
|
|
|
|
|
|
if branch.switch_pad.parent().as_ref() == Some(switch.upcast_ref()) {
|
|
|
|
switch.release_request_pad(&branch.switch_pad);
|
|
|
|
}
|
|
|
|
|
|
|
|
let ghostpad = source
|
|
|
|
.source
|
|
|
|
.static_pad(if is_video { "video" } else { "audio" })
|
|
|
|
.unwrap();
|
|
|
|
let _ = ghostpad.set_active(false);
|
|
|
|
source.source.remove_pad(&ghostpad).unwrap();
|
|
|
|
|
2022-10-09 13:06:59 +00:00
|
|
|
self.unblock_pads(state, fallback_source);
|
2020-05-20 09:02:53 +00:00
|
|
|
|
2020-04-08 12:11:16 +00:00
|
|
|
drop(state_guard);
|
2022-10-23 20:03:22 +00:00
|
|
|
self.obj().notify("status");
|
2020-04-08 12:11:16 +00:00
|
|
|
}
|
|
|
|
|
2022-10-09 13:06:59 +00:00
|
|
|
fn handle_buffering(&self, m: &gst::message::Buffering) {
|
2021-09-13 15:08:45 +00:00
|
|
|
let mut state_guard = self.state.lock();
|
2020-04-08 12:11:16 +00:00
|
|
|
let state = match &mut *state_guard {
|
|
|
|
None => {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
Some(state) => state,
|
|
|
|
};
|
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
let src = match m.src() {
|
|
|
|
Some(src) => src,
|
|
|
|
None => return,
|
|
|
|
};
|
|
|
|
|
|
|
|
let fallback_source = if let Some(ref source) = state.fallback_source {
|
|
|
|
src.has_as_ancestor(&source.source)
|
|
|
|
} else if src.has_as_ancestor(&state.source.source) {
|
|
|
|
false
|
|
|
|
} else {
|
|
|
|
return;
|
|
|
|
};
|
|
|
|
|
|
|
|
let source = if fallback_source {
|
|
|
|
if let Some(ref mut source) = state.fallback_source {
|
|
|
|
source
|
|
|
|
} else {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
&mut state.source
|
|
|
|
};
|
|
|
|
|
|
|
|
if source.pending_restart {
|
2022-10-09 13:06:59 +00:00
|
|
|
gst::debug!(CAT, imp: self, "Has pending restart");
|
2020-10-21 16:47:52 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
gst::debug!(
|
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: self,
|
2022-08-19 14:34:17 +00:00
|
|
|
"Got buffering {}% (fallback: {})",
|
|
|
|
m.percent(),
|
|
|
|
fallback_source
|
|
|
|
);
|
|
|
|
|
|
|
|
let buffering_percent = if fallback_source {
|
|
|
|
&mut state.stats.fallback_buffering_percent
|
|
|
|
} else {
|
|
|
|
&mut state.stats.buffering_percent
|
|
|
|
};
|
|
|
|
let last_buffering_update = if fallback_source {
|
|
|
|
&mut state.fallback_last_buffering_update
|
|
|
|
} else {
|
|
|
|
&mut state.last_buffering_update
|
|
|
|
};
|
2020-04-08 12:11:16 +00:00
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
*buffering_percent = m.percent();
|
|
|
|
if *buffering_percent < 100 {
|
|
|
|
*last_buffering_update = Some(Instant::now());
|
2020-05-19 13:51:59 +00:00
|
|
|
// Block source pads if needed to pause
|
2022-08-19 14:34:17 +00:00
|
|
|
for stream in [state.audio_stream.as_mut(), state.video_stream.as_mut()]
|
|
|
|
.iter_mut()
|
|
|
|
.flatten()
|
|
|
|
{
|
|
|
|
let branch = match stream {
|
|
|
|
Stream {
|
|
|
|
main_branch: Some(ref mut branch),
|
|
|
|
..
|
|
|
|
} if !fallback_source => branch,
|
|
|
|
Stream {
|
|
|
|
fallback_branch: Some(ref mut branch),
|
|
|
|
..
|
|
|
|
} if fallback_source => branch,
|
|
|
|
_ => continue,
|
|
|
|
};
|
|
|
|
|
|
|
|
if branch.source_srcpad_block.is_none() {
|
|
|
|
branch.source_srcpad_block = Some(self.add_pad_probe(
|
|
|
|
&branch.source_srcpad,
|
|
|
|
&branch.queue_srcpad,
|
|
|
|
fallback_source,
|
|
|
|
));
|
2020-04-08 12:11:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Check if we can unblock now
|
2022-10-09 13:06:59 +00:00
|
|
|
self.unblock_pads(state, fallback_source);
|
2020-04-08 12:11:16 +00:00
|
|
|
}
|
2020-10-29 13:39:58 +00:00
|
|
|
|
|
|
|
drop(state_guard);
|
2022-10-23 20:03:22 +00:00
|
|
|
self.obj().notify("status");
|
|
|
|
self.obj().notify("statistics");
|
2020-04-08 12:11:16 +00:00
|
|
|
}
|
|
|
|
|
2022-10-09 13:06:59 +00:00
|
|
|
fn handle_streams_selected(&self, m: &gst::message::StreamsSelected) {
|
2021-09-13 15:08:45 +00:00
|
|
|
let mut state_guard = self.state.lock();
|
2020-04-08 12:11:16 +00:00
|
|
|
let state = match &mut *state_guard {
|
|
|
|
None => {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
Some(state) => state,
|
|
|
|
};
|
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
let src = match m.src() {
|
|
|
|
Some(src) => src,
|
|
|
|
None => return,
|
|
|
|
};
|
|
|
|
|
|
|
|
let fallback_source = if let Some(ref source) = state.fallback_source {
|
|
|
|
src.has_as_ancestor(&source.source)
|
|
|
|
} else if src.has_as_ancestor(&state.source.source) {
|
|
|
|
false
|
|
|
|
} else {
|
|
|
|
return;
|
|
|
|
};
|
|
|
|
|
2021-04-12 12:49:54 +00:00
|
|
|
let streams = m.stream_collection();
|
2020-04-08 12:11:16 +00:00
|
|
|
|
2022-02-21 17:43:46 +00:00
|
|
|
gst::debug!(
|
2020-04-08 12:11:16 +00:00
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: self,
|
2022-08-19 14:34:17 +00:00
|
|
|
"Got stream collection {:?} (fallback: {})",
|
|
|
|
streams.debug(),
|
|
|
|
fallback_source,
|
2020-04-08 12:11:16 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
let mut have_audio = false;
|
|
|
|
let mut have_video = false;
|
|
|
|
for stream in streams.iter() {
|
2021-04-12 12:49:54 +00:00
|
|
|
have_audio = have_audio || stream.stream_type().contains(gst::StreamType::AUDIO);
|
|
|
|
have_video = have_video || stream.stream_type().contains(gst::StreamType::VIDEO);
|
2020-04-08 12:11:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if !have_audio && state.settings.enable_audio {
|
2022-10-09 13:06:59 +00:00
|
|
|
gst::warning!(CAT, imp: self, "Have no audio streams but audio is enabled");
|
2020-04-08 12:11:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if !have_video && state.settings.enable_video {
|
2022-10-09 13:06:59 +00:00
|
|
|
gst::warning!(CAT, imp: self, "Have no video streams but video is enabled");
|
2020-04-08 12:11:16 +00:00
|
|
|
}
|
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
if fallback_source {
|
|
|
|
if let Some(ref mut source) = state.fallback_source {
|
|
|
|
source.streams = Some(streams);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
state.source.streams = Some(streams);
|
|
|
|
}
|
2020-04-08 12:11:16 +00:00
|
|
|
|
2020-05-20 09:03:21 +00:00
|
|
|
// This might not be the first stream collection and we might have some unblocked pads from
|
|
|
|
// before already, which would need to be blocked again now for keeping things in sync
|
2022-08-19 14:34:17 +00:00
|
|
|
for branch in [state.video_stream.as_mut(), state.audio_stream.as_mut()]
|
2020-05-20 09:03:21 +00:00
|
|
|
.iter_mut()
|
2022-08-19 14:34:17 +00:00
|
|
|
.flatten()
|
|
|
|
.filter_map(|s| {
|
|
|
|
if fallback_source {
|
|
|
|
s.fallback_branch.as_mut()
|
|
|
|
} else {
|
|
|
|
s.main_branch.as_mut()
|
|
|
|
}
|
|
|
|
})
|
2020-05-20 09:03:21 +00:00
|
|
|
{
|
2022-08-19 14:34:17 +00:00
|
|
|
if branch.source_srcpad_block.is_none() {
|
|
|
|
branch.source_srcpad_block = Some(self.add_pad_probe(
|
|
|
|
&branch.source_srcpad,
|
|
|
|
&branch.queue_srcpad,
|
|
|
|
fallback_source,
|
|
|
|
));
|
2020-05-20 09:03:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-09 13:06:59 +00:00
|
|
|
self.unblock_pads(state, fallback_source);
|
2020-04-08 12:11:16 +00:00
|
|
|
|
|
|
|
drop(state_guard);
|
2022-10-23 20:03:22 +00:00
|
|
|
self.obj().notify("status");
|
2020-04-08 12:11:16 +00:00
|
|
|
}
|
|
|
|
|
2022-10-09 13:06:59 +00:00
|
|
|
fn handle_error(&self, m: &gst::message::Error) -> bool {
|
2021-09-13 15:08:45 +00:00
|
|
|
let mut state_guard = self.state.lock();
|
2020-04-08 12:11:16 +00:00
|
|
|
let state = match &mut *state_guard {
|
|
|
|
None => {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
Some(state) => state,
|
|
|
|
};
|
|
|
|
|
2023-01-05 10:32:01 +00:00
|
|
|
let src = match m.src().and_then(|s| s.downcast_ref::<gst::Element>()) {
|
2020-04-08 12:11:16 +00:00
|
|
|
None => return false,
|
|
|
|
Some(src) => src,
|
|
|
|
};
|
|
|
|
|
2022-02-21 17:43:46 +00:00
|
|
|
gst::debug!(
|
2020-04-08 12:11:16 +00:00
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: self,
|
2020-04-08 12:11:16 +00:00
|
|
|
"Got error message from {}",
|
2021-04-12 12:49:54 +00:00
|
|
|
src.path_string()
|
2020-04-08 12:11:16 +00:00
|
|
|
);
|
|
|
|
|
2023-01-05 10:32:01 +00:00
|
|
|
if src == &state.source.source || src.has_as_ancestor(&state.source.source) {
|
2022-10-09 13:06:59 +00:00
|
|
|
self.handle_source_error(state, RetryReason::Error, false);
|
2020-04-08 12:11:16 +00:00
|
|
|
drop(state_guard);
|
2022-10-23 20:03:22 +00:00
|
|
|
self.obj().notify("status");
|
|
|
|
self.obj().notify("statistics");
|
2020-04-08 12:11:16 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
// Check if error is from fallback input and if so, use a dummy fallback
|
|
|
|
if let Some(ref source) = state.fallback_source {
|
2023-01-05 10:32:01 +00:00
|
|
|
if src == &source.source || src.has_as_ancestor(&source.source) {
|
2022-10-09 13:06:59 +00:00
|
|
|
self.handle_source_error(state, RetryReason::Error, true);
|
2022-08-19 14:34:17 +00:00
|
|
|
drop(state_guard);
|
2022-10-23 20:03:22 +00:00
|
|
|
self.obj().notify("status");
|
|
|
|
self.obj().notify("statistics");
|
2022-10-04 13:42:14 +00:00
|
|
|
return true;
|
2020-12-10 16:16:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-21 17:43:46 +00:00
|
|
|
gst::error!(
|
2020-12-10 16:16:20 +00:00
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: self,
|
2020-12-10 16:16:20 +00:00
|
|
|
"Give up for error message from {}",
|
2021-04-12 12:49:54 +00:00
|
|
|
src.path_string()
|
2020-12-10 16:16:20 +00:00
|
|
|
);
|
|
|
|
|
2020-04-08 12:11:16 +00:00
|
|
|
false
|
|
|
|
}
|
|
|
|
|
2022-10-09 13:06:59 +00:00
|
|
|
fn handle_source_error(&self, state: &mut State, reason: RetryReason, fallback_source: bool) {
|
2022-08-19 14:34:17 +00:00
|
|
|
gst::debug!(
|
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: self,
|
2022-08-19 14:34:17 +00:00
|
|
|
"Handling source error (fallback: {}): {:?}",
|
|
|
|
fallback_source,
|
|
|
|
reason
|
|
|
|
);
|
|
|
|
|
|
|
|
if fallback_source {
|
|
|
|
state.stats.last_fallback_retry_reason = reason;
|
|
|
|
} else {
|
|
|
|
state.stats.last_retry_reason = reason;
|
|
|
|
}
|
|
|
|
|
|
|
|
let source = if fallback_source {
|
|
|
|
state.fallback_source.as_mut().unwrap()
|
|
|
|
} else {
|
|
|
|
&mut state.source
|
|
|
|
};
|
2020-10-29 13:39:58 +00:00
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
if source.pending_restart {
|
|
|
|
gst::debug!(
|
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: self,
|
2022-08-19 14:34:17 +00:00
|
|
|
"{}source is already pending restart",
|
|
|
|
if fallback_source { "fallback " } else { "" }
|
|
|
|
);
|
2020-04-08 12:11:16 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-10-29 13:39:58 +00:00
|
|
|
// Increase retry count only if there was no pending restart
|
2022-08-19 14:34:17 +00:00
|
|
|
if fallback_source {
|
|
|
|
state.stats.num_fallback_retry += 1;
|
|
|
|
} else {
|
|
|
|
state.stats.num_retry += 1;
|
|
|
|
}
|
2020-10-29 13:39:58 +00:00
|
|
|
|
2020-05-21 07:20:16 +00:00
|
|
|
// Unschedule pending timeout, we're restarting now
|
2022-08-19 14:34:17 +00:00
|
|
|
if let Some(timeout) = source.restart_timeout.take() {
|
2020-05-21 07:20:16 +00:00
|
|
|
timeout.unschedule();
|
|
|
|
}
|
|
|
|
|
2020-04-08 12:11:16 +00:00
|
|
|
// Prevent state changes from changing the state in an uncoordinated way
|
2022-08-19 14:34:17 +00:00
|
|
|
source.pending_restart = true;
|
2020-04-08 12:11:16 +00:00
|
|
|
|
|
|
|
// Drop any EOS events from any source pads of the source that might happen because of the
|
|
|
|
// error. We don't need to remove these pad probes because restarting the source will also
|
|
|
|
// remove/add the pads again.
|
2022-08-19 14:34:17 +00:00
|
|
|
for pad in source.source.src_pads() {
|
2023-10-16 16:16:52 +00:00
|
|
|
pad.add_probe(gst::PadProbeType::EVENT_DOWNSTREAM, |_pad, info| {
|
|
|
|
let Some(ev) = info.event() else {
|
|
|
|
return gst::PadProbeReturn::Pass;
|
|
|
|
};
|
|
|
|
|
|
|
|
if ev.type_() != gst::EventType::Eos {
|
|
|
|
return gst::PadProbeReturn::Pass;
|
|
|
|
}
|
|
|
|
|
|
|
|
gst::PadProbeReturn::Drop
|
|
|
|
})
|
2020-04-08 12:11:16 +00:00
|
|
|
.unwrap();
|
|
|
|
}
|
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
let source_weak = source.source.downgrade();
|
2022-10-23 20:03:22 +00:00
|
|
|
self.obj().call_async(move |element| {
|
2022-10-09 13:06:59 +00:00
|
|
|
let imp = element.imp();
|
2020-08-04 07:37:08 +00:00
|
|
|
|
2020-04-08 12:11:16 +00:00
|
|
|
let source = match source_weak.upgrade() {
|
|
|
|
None => return,
|
|
|
|
Some(source) => source,
|
|
|
|
};
|
|
|
|
|
2020-08-04 07:37:08 +00:00
|
|
|
// Remove blocking pad probes if they are still there as otherwise shutting down the
|
|
|
|
// source will deadlock on the probes.
|
2022-10-09 13:06:59 +00:00
|
|
|
let mut state_guard = imp.state.lock();
|
2020-08-04 07:37:08 +00:00
|
|
|
let state = match &mut *state_guard {
|
2022-08-19 14:34:17 +00:00
|
|
|
None => {
|
|
|
|
gst::debug!(
|
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: imp,
|
2022-08-19 14:34:17 +00:00
|
|
|
"Restarting {}source not needed anymore",
|
|
|
|
if fallback_source { "fallback " } else { "" }
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
Some(State {
|
|
|
|
source:
|
|
|
|
SourceBin {
|
|
|
|
pending_restart: false,
|
|
|
|
..
|
|
|
|
},
|
2020-08-04 07:37:08 +00:00
|
|
|
..
|
2022-08-19 14:34:17 +00:00
|
|
|
}) if !fallback_source => {
|
|
|
|
gst::debug!(
|
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: imp,
|
2022-08-19 14:34:17 +00:00
|
|
|
"Restarting {}source not needed anymore",
|
|
|
|
if fallback_source { "fallback " } else { "" }
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
Some(State {
|
|
|
|
fallback_source:
|
|
|
|
Some(SourceBin {
|
|
|
|
pending_restart: false,
|
|
|
|
..
|
|
|
|
}),
|
|
|
|
..
|
|
|
|
}) if fallback_source => {
|
|
|
|
gst::debug!(
|
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: imp,
|
2022-08-19 14:34:17 +00:00
|
|
|
"Restarting {}source not needed anymore",
|
|
|
|
if fallback_source { "fallback " } else { "" }
|
|
|
|
);
|
2020-08-04 07:37:08 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
Some(state) => state,
|
|
|
|
};
|
2022-08-19 14:34:17 +00:00
|
|
|
for (source_srcpad, block) in [state.video_stream.as_mut(), state.audio_stream.as_mut()]
|
|
|
|
.iter_mut()
|
|
|
|
.flatten()
|
|
|
|
.filter_map(|s| {
|
|
|
|
if fallback_source {
|
|
|
|
s.fallback_branch.as_mut()
|
|
|
|
} else {
|
|
|
|
s.main_branch.as_mut()
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.filter_map(|branch| {
|
|
|
|
if let Some(block) = branch.source_srcpad_block.take() {
|
|
|
|
Some((&branch.source_srcpad, block))
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
})
|
2020-08-04 07:37:08 +00:00
|
|
|
{
|
2022-02-21 17:43:46 +00:00
|
|
|
gst::debug!(
|
2020-08-04 07:37:08 +00:00
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: imp,
|
2020-08-04 07:37:08 +00:00
|
|
|
"Removing pad probe for pad {}",
|
2022-08-19 14:34:17 +00:00
|
|
|
source_srcpad.name()
|
2020-08-04 07:37:08 +00:00
|
|
|
);
|
|
|
|
block.pad.remove_probe(block.probe_id);
|
2022-10-19 14:03:15 +00:00
|
|
|
block.pad.remove_probe(block.qos_probe_id);
|
2020-08-04 07:37:08 +00:00
|
|
|
}
|
2022-08-19 14:34:17 +00:00
|
|
|
let switch_sinkpads = [state.audio_stream.as_ref(), state.video_stream.as_ref()]
|
2022-04-12 11:40:08 +00:00
|
|
|
.into_iter()
|
|
|
|
.flatten()
|
2022-08-19 14:34:17 +00:00
|
|
|
.filter_map(|s| {
|
|
|
|
if fallback_source {
|
|
|
|
s.fallback_branch.as_ref()
|
2022-04-12 11:40:08 +00:00
|
|
|
} else {
|
2022-08-19 14:34:17 +00:00
|
|
|
s.main_branch.as_ref()
|
2022-04-12 11:40:08 +00:00
|
|
|
}
|
|
|
|
})
|
2022-08-19 14:34:17 +00:00
|
|
|
.map(|branch| branch.switch_pad.clone())
|
2022-04-12 14:16:17 +00:00
|
|
|
.collect::<Vec<_>>();
|
2020-08-04 07:37:08 +00:00
|
|
|
drop(state_guard);
|
|
|
|
|
2022-10-09 13:06:59 +00:00
|
|
|
gst::debug!(CAT, imp: imp, "Flushing source");
|
2022-08-19 14:34:17 +00:00
|
|
|
for pad in switch_sinkpads {
|
|
|
|
let _ = pad.push_event(gst::event::FlushStart::builder().build());
|
|
|
|
if let Some(switch) = pad.parent().map(|p| p.downcast::<gst::Element>().unwrap()) {
|
|
|
|
switch.release_request_pad(&pad);
|
|
|
|
}
|
2022-04-12 11:40:08 +00:00
|
|
|
}
|
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
gst::debug!(
|
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: imp,
|
2022-08-19 14:34:17 +00:00
|
|
|
"Shutting down {}source",
|
|
|
|
if fallback_source { "fallback " } else { "" }
|
|
|
|
);
|
|
|
|
let _ = source.set_state(gst::State::Null);
|
2022-04-12 14:16:17 +00:00
|
|
|
|
2020-04-08 12:11:16 +00:00
|
|
|
// Sleep for 1s before retrying
|
|
|
|
|
2022-10-09 13:06:59 +00:00
|
|
|
let mut state_guard = imp.state.lock();
|
2020-04-08 12:11:16 +00:00
|
|
|
let state = match &mut *state_guard {
|
2022-08-19 14:34:17 +00:00
|
|
|
None => {
|
|
|
|
gst::debug!(
|
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: imp,
|
2022-08-19 14:34:17 +00:00
|
|
|
"Restarting {}source not needed anymore",
|
|
|
|
if fallback_source { "fallback " } else { "" }
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
Some(State {
|
|
|
|
source:
|
|
|
|
SourceBin {
|
|
|
|
pending_restart: false,
|
|
|
|
..
|
|
|
|
},
|
2020-04-08 12:11:16 +00:00
|
|
|
..
|
2022-08-19 14:34:17 +00:00
|
|
|
}) if !fallback_source => {
|
|
|
|
gst::debug!(
|
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: imp,
|
2022-08-19 14:34:17 +00:00
|
|
|
"Restarting {}source not needed anymore",
|
|
|
|
if fallback_source { "fallback " } else { "" }
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
Some(State {
|
|
|
|
fallback_source:
|
|
|
|
Some(SourceBin {
|
|
|
|
pending_restart: false,
|
|
|
|
..
|
|
|
|
}),
|
|
|
|
..
|
|
|
|
}) if fallback_source => {
|
|
|
|
gst::debug!(
|
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: imp,
|
2022-08-19 14:34:17 +00:00
|
|
|
"Restarting {}source not needed anymore",
|
|
|
|
if fallback_source { "fallback " } else { "" }
|
|
|
|
);
|
2020-04-08 12:11:16 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
Some(state) => state,
|
|
|
|
};
|
2020-10-21 11:39:34 +00:00
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
for branch in [state.video_stream.as_mut(), state.audio_stream.as_mut()]
|
2020-10-21 11:39:34 +00:00
|
|
|
.iter_mut()
|
2022-08-19 14:34:17 +00:00
|
|
|
.flatten()
|
|
|
|
.filter_map(|s| {
|
|
|
|
if fallback_source {
|
|
|
|
s.fallback_branch.as_mut()
|
|
|
|
} else {
|
|
|
|
s.main_branch.as_mut()
|
|
|
|
}
|
|
|
|
})
|
2020-10-21 11:39:34 +00:00
|
|
|
{
|
2022-08-19 14:34:17 +00:00
|
|
|
branch.source_srcpad_block = None;
|
2020-10-21 11:39:34 +00:00
|
|
|
}
|
|
|
|
|
2022-10-09 13:06:59 +00:00
|
|
|
gst::debug!(CAT, imp: imp, "Waiting for 1s before retrying");
|
2020-04-08 12:11:16 +00:00
|
|
|
let clock = gst::SystemClock::obtain();
|
2021-05-28 16:35:28 +00:00
|
|
|
let wait_time = clock.time().unwrap() + gst::ClockTime::SECOND;
|
2022-08-19 14:34:17 +00:00
|
|
|
if fallback_source {
|
|
|
|
assert!(state
|
|
|
|
.fallback_source
|
|
|
|
.as_ref()
|
|
|
|
.map(|s| s.pending_restart_timeout.is_none())
|
|
|
|
.unwrap_or(true));
|
|
|
|
} else {
|
|
|
|
assert!(state.source.pending_restart_timeout.is_none());
|
|
|
|
}
|
2020-04-08 12:11:16 +00:00
|
|
|
|
2020-10-20 09:49:51 +00:00
|
|
|
let timeout = clock.new_single_shot_id(wait_time);
|
2020-04-08 12:11:16 +00:00
|
|
|
let element_weak = element.downgrade();
|
|
|
|
timeout
|
|
|
|
.wait_async(move |_clock, _time, _id| {
|
|
|
|
let element = match element_weak.upgrade() {
|
|
|
|
None => return,
|
|
|
|
Some(element) => element,
|
|
|
|
};
|
|
|
|
|
2022-10-23 18:46:18 +00:00
|
|
|
gst::debug!(CAT, obj: element, "Woke up, retrying");
|
2022-08-19 14:34:17 +00:00
|
|
|
element.call_async(move |element| {
|
2022-10-09 13:06:59 +00:00
|
|
|
let imp = element.imp();
|
2020-04-08 12:11:16 +00:00
|
|
|
|
2022-10-09 13:06:59 +00:00
|
|
|
let mut state_guard = imp.state.lock();
|
2020-04-08 12:11:16 +00:00
|
|
|
let state = match &mut *state_guard {
|
2022-08-19 14:34:17 +00:00
|
|
|
None => {
|
|
|
|
gst::debug!(
|
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: imp,
|
2022-08-19 14:34:17 +00:00
|
|
|
"Restarting {}source not needed anymore",
|
|
|
|
if fallback_source { "fallback " } else { "" }
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
Some(State {
|
|
|
|
source:
|
|
|
|
SourceBin {
|
|
|
|
pending_restart: false,
|
|
|
|
..
|
|
|
|
},
|
2020-04-08 12:11:16 +00:00
|
|
|
..
|
2022-08-19 14:34:17 +00:00
|
|
|
}) if !fallback_source => {
|
2022-02-21 17:43:46 +00:00
|
|
|
gst::debug!(
|
2020-04-08 12:11:16 +00:00
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: imp,
|
2022-08-19 14:34:17 +00:00
|
|
|
"Restarting {}source not needed anymore",
|
|
|
|
if fallback_source { "fallback " } else { "" }
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
Some(State {
|
|
|
|
fallback_source:
|
|
|
|
Some(SourceBin {
|
|
|
|
pending_restart: false,
|
|
|
|
..
|
|
|
|
}),
|
|
|
|
..
|
|
|
|
}) if fallback_source => {
|
|
|
|
gst::debug!(
|
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: imp,
|
2022-08-19 14:34:17 +00:00
|
|
|
"Restarting {}source not needed anymore",
|
|
|
|
if fallback_source { "fallback " } else { "" }
|
2020-04-08 12:11:16 +00:00
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
Some(state) => state,
|
|
|
|
};
|
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
let (source, old_source) = if !fallback_source {
|
|
|
|
if let Source::Uri(..) = state.configured_source {
|
|
|
|
// FIXME: Create a new uridecodebin3 because it currently is not reusable
|
|
|
|
// See https://gitlab.freedesktop.org/gstreamer/gst-plugins-base/-/issues/746
|
|
|
|
element.remove(&state.source.source).unwrap();
|
2020-04-08 12:11:16 +00:00
|
|
|
|
2022-10-09 13:06:59 +00:00
|
|
|
let source = imp.create_main_input(
|
2022-08-19 14:34:17 +00:00
|
|
|
&state.configured_source,
|
|
|
|
state.settings.buffer_duration,
|
|
|
|
);
|
2020-04-08 12:11:16 +00:00
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
(
|
|
|
|
source.source.clone(),
|
|
|
|
Some(mem::replace(&mut state.source, source)),
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
state.source.pending_restart = false;
|
|
|
|
state.source.pending_restart_timeout = None;
|
|
|
|
state.stats.buffering_percent = 100;
|
|
|
|
state.last_buffering_update = None;
|
|
|
|
|
|
|
|
if let Some(timeout) = state.source.restart_timeout.take() {
|
2022-10-09 13:06:59 +00:00
|
|
|
gst::debug!(CAT, imp: imp, "Unscheduling restart timeout");
|
2022-08-19 14:34:17 +00:00
|
|
|
timeout.unschedule();
|
|
|
|
}
|
|
|
|
|
|
|
|
(state.source.source.clone(), None)
|
|
|
|
}
|
|
|
|
} else if let Some(ref mut source) = state.fallback_source {
|
|
|
|
source.pending_restart = false;
|
|
|
|
source.pending_restart_timeout = None;
|
|
|
|
state.stats.fallback_buffering_percent = 100;
|
|
|
|
state.fallback_last_buffering_update = None;
|
|
|
|
|
|
|
|
if let Some(timeout) = source.restart_timeout.take() {
|
2022-10-09 13:06:59 +00:00
|
|
|
gst::debug!(CAT, imp: imp, "Unscheduling restart timeout");
|
2022-08-19 14:34:17 +00:00
|
|
|
timeout.unschedule();
|
|
|
|
}
|
|
|
|
|
|
|
|
(source.source.clone(), None)
|
2020-05-19 13:51:59 +00:00
|
|
|
} else {
|
2022-08-19 14:34:17 +00:00
|
|
|
return;
|
2020-05-19 13:51:59 +00:00
|
|
|
};
|
2020-04-08 12:11:16 +00:00
|
|
|
|
|
|
|
drop(state_guard);
|
|
|
|
|
2020-05-19 13:51:59 +00:00
|
|
|
if let Some(old_source) = old_source {
|
|
|
|
// Drop old source after releasing the lock, it might call the pad-removed callback
|
|
|
|
// still
|
|
|
|
drop(old_source);
|
|
|
|
}
|
2020-04-08 12:11:16 +00:00
|
|
|
|
|
|
|
if source.sync_state_with_parent().is_err() {
|
2022-08-19 14:34:17 +00:00
|
|
|
gst::error!(
|
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: imp,
|
2022-08-19 14:34:17 +00:00
|
|
|
"{}source failed to change state",
|
|
|
|
if fallback_source { "fallback " } else { "" }
|
|
|
|
);
|
2020-05-08 10:55:21 +00:00
|
|
|
let _ = source.set_state(gst::State::Null);
|
2022-10-09 13:06:59 +00:00
|
|
|
let mut state_guard = imp.state.lock();
|
2020-04-08 12:11:16 +00:00
|
|
|
let state = state_guard.as_mut().expect("no state");
|
2022-10-09 13:06:59 +00:00
|
|
|
imp.handle_source_error(
|
2020-10-29 13:39:58 +00:00
|
|
|
state,
|
|
|
|
RetryReason::StateChangeFailure,
|
2022-08-19 14:34:17 +00:00
|
|
|
fallback_source,
|
2020-10-29 13:39:58 +00:00
|
|
|
);
|
|
|
|
drop(state_guard);
|
|
|
|
element.notify("statistics");
|
2020-07-02 09:23:48 +00:00
|
|
|
} else {
|
2022-10-09 13:06:59 +00:00
|
|
|
let mut state_guard = imp.state.lock();
|
2020-07-02 09:23:48 +00:00
|
|
|
let state = state_guard.as_mut().expect("no state");
|
2022-08-19 14:34:17 +00:00
|
|
|
if fallback_source {
|
|
|
|
assert!(state
|
|
|
|
.fallback_source
|
|
|
|
.as_ref()
|
|
|
|
.map(|s| s.restart_timeout.is_none())
|
|
|
|
.unwrap_or(true));
|
|
|
|
} else {
|
|
|
|
assert!(state.source.restart_timeout.is_none());
|
|
|
|
}
|
2022-10-09 13:06:59 +00:00
|
|
|
imp.schedule_source_restart_timeout(
|
2021-05-28 16:35:28 +00:00
|
|
|
state,
|
|
|
|
gst::ClockTime::ZERO,
|
2022-08-19 14:34:17 +00:00
|
|
|
fallback_source,
|
2021-05-28 16:35:28 +00:00
|
|
|
);
|
2020-04-08 12:11:16 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
})
|
|
|
|
.expect("Failed to wait async");
|
2022-08-19 14:34:17 +00:00
|
|
|
if fallback_source {
|
|
|
|
if let Some(ref mut source) = state.fallback_source {
|
|
|
|
source.pending_restart_timeout = Some(timeout);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
state.source.pending_restart_timeout = Some(timeout);
|
|
|
|
}
|
2020-04-08 12:11:16 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-07-28 14:00:48 +00:00
|
|
|
#[allow(clippy::blocks_in_if_conditions)]
|
2020-07-02 09:23:48 +00:00
|
|
|
fn schedule_source_restart_timeout(
|
|
|
|
&self,
|
|
|
|
state: &mut State,
|
|
|
|
elapsed: gst::ClockTime,
|
2022-08-19 14:34:17 +00:00
|
|
|
fallback_source: bool,
|
2020-07-02 09:23:48 +00:00
|
|
|
) {
|
2022-08-19 14:34:17 +00:00
|
|
|
if fallback_source {
|
|
|
|
gst::fixme!(
|
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: self,
|
2022-08-19 14:34:17 +00:00
|
|
|
"Restart timeout not implemented for fallback source"
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let source = if fallback_source {
|
|
|
|
if let Some(ref mut source) = state.fallback_source {
|
|
|
|
source
|
|
|
|
} else {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
&mut state.source
|
|
|
|
};
|
|
|
|
|
|
|
|
if source.pending_restart {
|
2022-02-21 17:43:46 +00:00
|
|
|
gst::debug!(
|
2020-08-04 07:36:45 +00:00
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: self,
|
2022-08-19 14:34:17 +00:00
|
|
|
"Not scheduling {}source restart timeout because source is pending restart already",
|
|
|
|
if fallback_source { "fallback " } else { "" },
|
2020-08-04 07:36:45 +00:00
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
if source.is_image {
|
2022-02-21 17:43:46 +00:00
|
|
|
gst::debug!(
|
2021-06-03 23:29:09 +00:00
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: self,
|
2022-08-19 14:34:17 +00:00
|
|
|
"Not scheduling {}source restart timeout because we are playing back an image",
|
|
|
|
if fallback_source { "fallback " } else { "" },
|
2021-06-03 23:29:09 +00:00
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
if !fallback_source && state.manually_blocked {
|
2022-02-21 17:43:46 +00:00
|
|
|
gst::debug!(
|
2021-05-27 23:43:50 +00:00
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: self,
|
2021-05-27 23:43:50 +00:00
|
|
|
"Not scheduling source restart timeout because we are manually blocked",
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-07-02 09:23:48 +00:00
|
|
|
let clock = gst::SystemClock::obtain();
|
2021-05-28 16:35:28 +00:00
|
|
|
let wait_time = clock.time().unwrap() + state.settings.restart_timeout - elapsed;
|
2022-02-21 17:43:46 +00:00
|
|
|
gst::debug!(
|
2020-07-02 09:23:48 +00:00
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: self,
|
2022-08-19 14:34:17 +00:00
|
|
|
"Scheduling {}source restart timeout for {}",
|
|
|
|
if fallback_source { "fallback " } else { "" },
|
2020-07-02 09:23:48 +00:00
|
|
|
wait_time,
|
|
|
|
);
|
|
|
|
|
2020-10-20 09:49:51 +00:00
|
|
|
let timeout = clock.new_single_shot_id(wait_time);
|
2022-10-23 20:03:22 +00:00
|
|
|
let element_weak = self.obj().downgrade();
|
2020-07-02 09:23:48 +00:00
|
|
|
timeout
|
|
|
|
.wait_async(move |_clock, _time, _id| {
|
|
|
|
let element = match element_weak.upgrade() {
|
|
|
|
None => return,
|
|
|
|
Some(element) => element,
|
|
|
|
};
|
|
|
|
|
|
|
|
element.call_async(move |element| {
|
2022-10-09 13:06:59 +00:00
|
|
|
let imp = element.imp();
|
2020-07-02 09:23:48 +00:00
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
gst::debug!(
|
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: imp,
|
2022-08-19 14:34:17 +00:00
|
|
|
"{}source restart timeout triggered",
|
|
|
|
if fallback_source { "fallback " } else { "" }
|
|
|
|
);
|
2022-10-09 13:06:59 +00:00
|
|
|
let mut state_guard = imp.state.lock();
|
2020-07-02 09:23:48 +00:00
|
|
|
let state = match &mut *state_guard {
|
|
|
|
None => {
|
2022-08-19 14:34:17 +00:00
|
|
|
gst::debug!(
|
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: imp,
|
2022-08-19 14:34:17 +00:00
|
|
|
"Restarting {}source not needed anymore",
|
|
|
|
if fallback_source { "fallback " } else { "" }
|
|
|
|
);
|
2020-07-02 09:23:48 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
Some(state) => state,
|
|
|
|
};
|
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
let source = if fallback_source {
|
|
|
|
if let Some(ref mut source) = state.fallback_source {
|
|
|
|
source
|
|
|
|
} else {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
&mut state.source
|
|
|
|
};
|
|
|
|
|
|
|
|
source.restart_timeout = None;
|
2020-07-02 09:23:48 +00:00
|
|
|
|
2020-07-02 15:16:35 +00:00
|
|
|
// If we have the fallback activated then restart the source now.
|
2022-10-09 13:06:59 +00:00
|
|
|
if fallback_source || imp.have_fallback_activated(state) {
|
2022-08-19 14:34:17 +00:00
|
|
|
let (last_buffering_update, buffering_percent) = if fallback_source {
|
|
|
|
(
|
|
|
|
state.fallback_last_buffering_update,
|
|
|
|
state.stats.fallback_buffering_percent,
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
(state.last_buffering_update, state.stats.buffering_percent)
|
|
|
|
};
|
2020-07-02 09:23:48 +00:00
|
|
|
// If we're not actively buffering right now let's restart the source
|
2022-08-19 14:34:17 +00:00
|
|
|
if last_buffering_update
|
2021-05-28 16:35:28 +00:00
|
|
|
.map(|i| i.elapsed() >= state.settings.restart_timeout.into())
|
2022-08-19 14:34:17 +00:00
|
|
|
.unwrap_or(buffering_percent == 100)
|
2020-07-02 09:23:48 +00:00
|
|
|
{
|
2022-08-19 14:34:17 +00:00
|
|
|
gst::debug!(
|
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: imp,
|
2022-08-19 14:34:17 +00:00
|
|
|
"Not buffering, restarting {}source",
|
|
|
|
if fallback_source { "fallback " } else { "" }
|
|
|
|
);
|
2020-07-02 09:23:48 +00:00
|
|
|
|
2022-10-09 13:06:59 +00:00
|
|
|
imp.handle_source_error(state, RetryReason::Timeout, fallback_source);
|
2020-10-29 13:39:58 +00:00
|
|
|
drop(state_guard);
|
|
|
|
element.notify("statistics");
|
2020-07-02 09:23:48 +00:00
|
|
|
} else {
|
2022-08-19 14:34:17 +00:00
|
|
|
gst::debug!(
|
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: imp,
|
2022-08-19 14:34:17 +00:00
|
|
|
"Buffering, restarting {}source later",
|
|
|
|
if fallback_source { "fallback " } else { "" }
|
|
|
|
);
|
|
|
|
let elapsed = last_buffering_update
|
2021-05-28 16:35:28 +00:00
|
|
|
.and_then(|last_buffering_update| {
|
|
|
|
gst::ClockTime::try_from(last_buffering_update.elapsed()).ok()
|
|
|
|
})
|
|
|
|
.unwrap_or(gst::ClockTime::ZERO);
|
2020-07-02 09:23:48 +00:00
|
|
|
|
2022-10-09 13:06:59 +00:00
|
|
|
imp.schedule_source_restart_timeout(state, elapsed, fallback_source);
|
2020-07-02 09:23:48 +00:00
|
|
|
}
|
|
|
|
} else {
|
2022-08-19 14:34:17 +00:00
|
|
|
gst::debug!(
|
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: imp,
|
2022-08-19 14:34:17 +00:00
|
|
|
"Restarting {}source not needed anymore",
|
|
|
|
if fallback_source { "fallback " } else { "" }
|
|
|
|
);
|
2020-07-02 09:23:48 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
})
|
|
|
|
.expect("Failed to wait async");
|
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
source.restart_timeout = Some(timeout);
|
2020-07-02 09:23:48 +00:00
|
|
|
}
|
|
|
|
|
2020-07-28 14:00:48 +00:00
|
|
|
#[allow(clippy::blocks_in_if_conditions)]
|
2022-10-09 13:06:59 +00:00
|
|
|
fn have_fallback_activated(&self, state: &State) -> bool {
|
2020-04-08 12:11:16 +00:00
|
|
|
let mut have_audio = false;
|
|
|
|
let mut have_video = false;
|
2022-08-19 14:34:17 +00:00
|
|
|
if let Some(ref streams) = state.source.streams {
|
2020-04-08 12:11:16 +00:00
|
|
|
for stream in streams.iter() {
|
2021-04-12 12:49:54 +00:00
|
|
|
have_audio = have_audio || stream.stream_type().contains(gst::StreamType::AUDIO);
|
|
|
|
have_video = have_video || stream.stream_type().contains(gst::StreamType::VIDEO);
|
2020-04-08 12:11:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we have neither audio nor video (no streams yet), or active pad for the ones we have
|
2020-07-02 15:16:35 +00:00
|
|
|
// is the fallback pad then we have the fallback activated.
|
|
|
|
(!have_audio && !have_video)
|
2020-04-08 12:11:16 +00:00
|
|
|
|| (have_audio
|
2020-07-30 11:04:37 +00:00
|
|
|
&& state.audio_stream.is_some()
|
2020-04-08 12:11:16 +00:00
|
|
|
&& state
|
|
|
|
.audio_stream
|
|
|
|
.as_ref()
|
2021-11-08 09:55:40 +00:00
|
|
|
.and_then(|s| s.switch.property::<Option<gst::Pad>>("active-pad"))
|
2021-09-13 15:08:45 +00:00
|
|
|
.map(|p| p.property::<u32>("priority") != 0)
|
2020-04-08 12:11:16 +00:00
|
|
|
.unwrap_or(true))
|
|
|
|
|| (have_video
|
2020-07-30 11:04:37 +00:00
|
|
|
&& state.video_stream.is_some()
|
2020-04-08 12:11:16 +00:00
|
|
|
&& state
|
|
|
|
.video_stream
|
|
|
|
.as_ref()
|
2021-11-08 09:55:40 +00:00
|
|
|
.and_then(|s| s.switch.property::<Option<gst::Pad>>("active-pad"))
|
2021-09-13 15:08:45 +00:00
|
|
|
.map(|p| p.property::<u32>("priority") != 0)
|
2020-04-08 12:11:16 +00:00
|
|
|
.unwrap_or(true))
|
2020-07-02 15:16:35 +00:00
|
|
|
}
|
|
|
|
|
2022-10-09 13:06:59 +00:00
|
|
|
fn handle_switch_active_pad_change(&self, is_audio: bool) {
|
2021-09-13 15:08:45 +00:00
|
|
|
let mut state_guard = self.state.lock();
|
2020-07-02 15:16:35 +00:00
|
|
|
let state = match &mut *state_guard {
|
|
|
|
None => {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
Some(state) => state,
|
|
|
|
};
|
|
|
|
|
|
|
|
// If we have the fallback activated then start the retry timeout unless it was started
|
|
|
|
// already. Otherwise cancel the retry timeout.
|
2022-10-09 13:06:59 +00:00
|
|
|
if self.have_fallback_activated(state) {
|
2022-08-19 14:34:17 +00:00
|
|
|
gst::warning!(
|
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: self,
|
2022-08-19 14:34:17 +00:00
|
|
|
"Switched to {} fallback stream",
|
|
|
|
if is_audio { "audio" } else { "video " }
|
|
|
|
);
|
|
|
|
if state.source.restart_timeout.is_none() {
|
2022-10-09 13:06:59 +00:00
|
|
|
self.schedule_source_restart_timeout(state, gst::ClockTime::ZERO, false);
|
2020-07-02 09:23:48 +00:00
|
|
|
}
|
|
|
|
} else {
|
2022-08-19 14:34:17 +00:00
|
|
|
gst::debug!(
|
|
|
|
CAT,
|
2022-10-09 13:06:59 +00:00
|
|
|
imp: self,
|
2022-08-19 14:34:17 +00:00
|
|
|
"Switched to {} main stream",
|
|
|
|
if is_audio { "audio" } else { "video" }
|
|
|
|
);
|
|
|
|
if let Some(timeout) = state.source.retry_timeout.take() {
|
2022-10-09 13:06:59 +00:00
|
|
|
gst::debug!(CAT, imp: self, "Unscheduling retry timeout");
|
2020-07-02 09:23:48 +00:00
|
|
|
timeout.unschedule();
|
2020-04-08 12:11:16 +00:00
|
|
|
}
|
|
|
|
|
2022-08-19 14:34:17 +00:00
|
|
|
if let Some(timeout) = state.source.restart_timeout.take() {
|
2022-10-09 13:06:59 +00:00
|
|
|
gst::debug!(CAT, imp: self, "Unscheduling restart timeout");
|
2020-07-02 09:23:48 +00:00
|
|
|
timeout.unschedule();
|
2020-04-08 12:11:16 +00:00
|
|
|
}
|
|
|
|
}
|
2021-06-04 22:16:05 +00:00
|
|
|
|
|
|
|
drop(state_guard);
|
2022-10-23 20:03:22 +00:00
|
|
|
self.obj().notify("status");
|
2020-04-08 12:11:16 +00:00
|
|
|
}
|
2020-10-29 13:39:58 +00:00
|
|
|
|
2021-04-12 12:49:54 +00:00
|
|
|
fn stats(&self) -> gst::Structure {
|
2021-09-13 15:08:45 +00:00
|
|
|
let state_guard = self.state.lock();
|
2020-10-29 13:39:58 +00:00
|
|
|
|
|
|
|
let state = match &*state_guard {
|
|
|
|
None => return Stats::default().to_structure(),
|
|
|
|
Some(ref state) => state,
|
|
|
|
};
|
|
|
|
|
|
|
|
state.stats.to_structure()
|
|
|
|
}
|
2020-04-08 12:11:16 +00:00
|
|
|
}
|