mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-09-27 04:28:59 +03:00
Add sub stream recording with adaptive quality playback (#24009)
* add sub stream recording with adaptive quality playback Optionally record a second, lower bitrate stream alongside the main recording stream via a `record_sub` input role and `record.sub` config block, with its own retention windows. Recordings rows now carry the stream type plus the media details needed to serve both streams from one manifest: video codec, audio presence, audio codec and rate, and a record-time keyframe index. Playback resolves coverage across both streams and merges them into a single VOD sequence, falling back to a discontinuity manifest with per-clip init segments when the media signatures differ. The player exposes a quality selector, and an auto governor picks the stream from stall time, bandwidth, codec support, and the save-data hint. * fix tests and i18n
This commit is contained in:
committed by
Nicolas Mowen
parent
f7c5500ea8
commit
1498231eb9
@@ -45,6 +45,13 @@ import {
|
||||
CommandList,
|
||||
} from "@/components/ui/command";
|
||||
|
||||
// Recording the sub stream from the same stream as record would just
|
||||
// re-record the main stream, so the two roles are mutually exclusive.
|
||||
const CONFLICTING_ROLES: Partial<Record<StreamRole, StreamRole>> = {
|
||||
record: "record_sub",
|
||||
record_sub: "record",
|
||||
};
|
||||
|
||||
type Step3StreamConfigProps = {
|
||||
wizardData: Partial<WizardFormData>;
|
||||
onUpdate: (data: Partial<WizardFormData>) => void;
|
||||
@@ -163,9 +170,12 @@ export default function Step3StreamConfig({
|
||||
const newRoles = stream.roles.filter((r) => r !== role);
|
||||
updateStream(streamId, { roles: newRoles });
|
||||
} else {
|
||||
// Check if role is already used in another stream
|
||||
const usedRoles = getUsedRolesExcludingStream(streamId);
|
||||
if (!usedRoles.has(role)) {
|
||||
const conflictingRole = CONFLICTING_ROLES[role];
|
||||
const hasConflict = conflictingRole
|
||||
? stream.roles.includes(conflictingRole)
|
||||
: false;
|
||||
if (!usedRoles.has(role) && !hasConflict) {
|
||||
// Allow adding the role
|
||||
const newRoles = [...stream.roles, role];
|
||||
updateStream(streamId, { roles: newRoles });
|
||||
@@ -617,6 +627,10 @@ export default function Step3StreamConfig({
|
||||
<strong>record</strong> -{" "}
|
||||
{t("cameraWizard.step3.rolesPopover.record")}
|
||||
</div>
|
||||
<div>
|
||||
<strong>record_sub</strong> -{" "}
|
||||
{t("cameraWizard.step3.rolesPopover.record_sub")}
|
||||
</div>
|
||||
<div>
|
||||
<strong>audio</strong> -{" "}
|
||||
{t("cameraWizard.step3.rolesPopover.audio")}
|
||||
@@ -639,25 +653,35 @@ export default function Step3StreamConfig({
|
||||
</div>
|
||||
<div className="rounded-lg bg-background p-3">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{(["detect", "record", "audio"] as const).map((role) => {
|
||||
const isUsedElsewhere = getUsedRolesExcludingStream(
|
||||
stream.id,
|
||||
).has(role);
|
||||
const isChecked = stream.roles.includes(role);
|
||||
return (
|
||||
<div
|
||||
key={role}
|
||||
className="flex w-full items-center justify-between"
|
||||
>
|
||||
<span className="text-sm capitalize">{role}</span>
|
||||
<Switch
|
||||
checked={isChecked}
|
||||
onCheckedChange={() => toggleRole(stream.id, role)}
|
||||
disabled={!isChecked && isUsedElsewhere}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{(["detect", "record", "record_sub", "audio"] as const).map(
|
||||
(role) => {
|
||||
const isUsedElsewhere = getUsedRolesExcludingStream(
|
||||
stream.id,
|
||||
).has(role);
|
||||
const conflictingRole = CONFLICTING_ROLES[role];
|
||||
const hasConflict = conflictingRole
|
||||
? stream.roles.includes(conflictingRole)
|
||||
: false;
|
||||
const isChecked = stream.roles.includes(role);
|
||||
return (
|
||||
<div
|
||||
key={role}
|
||||
className="flex w-full items-center justify-between"
|
||||
>
|
||||
<span className="text-sm capitalize">{role}</span>
|
||||
<Switch
|
||||
checked={isChecked}
|
||||
onCheckedChange={() =>
|
||||
toggleRole(stream.id, role)
|
||||
}
|
||||
disabled={
|
||||
!isChecked && (isUsedElsewhere || hasConflict)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user