Add dual-stream support for Frigate with optional sub-stream selection

Features:
- Optional sub-stream selection from already discovered streams
- No additional scanning required - reuse existing results
- UI: "Add Sub Stream" button to select secondary stream
- UI: "Remove Sub Stream" button to clear selection
- Smart stream routing in Frigate configs
- Go2RTC: generates _main and _sub stream names
- Frigate: detect on sub (CPU efficient), record on main (quality)
- Frigate: auto-detection of stream resolution
- Object detection: person, car, cat, dog
- Motion-based recording by default
- Live view streams configuration
- Support for any resolution: HD, 4K, 8K+
- Comprehensive documentation with examples
This commit is contained in:
eduard256
2025-11-06 22:53:50 +03:00
parent 74fe12bcf1
commit 7fd1d78ffa
8 changed files with 874 additions and 129 deletions
+75 -15
View File
@@ -556,12 +556,19 @@ body {
}
/* ===== CAROUSEL ===== */
.carousel {
.carousel-wrapper {
position: relative;
overflow: hidden;
display: flex;
align-items: center;
gap: var(--space-4);
margin-bottom: var(--space-4);
}
.carousel {
flex: 1;
overflow: hidden;
}
.carousel-track {
display: flex;
transition: transform var(--transition-slow);
@@ -621,9 +628,7 @@ body {
}
.carousel-arrow {
position: absolute;
top: 50%;
transform: translateY(-50%);
flex-shrink: 0;
width: 48px;
height: 48px;
background: var(--bg-elevated);
@@ -634,7 +639,6 @@ body {
justify-content: center;
cursor: pointer;
transition: all var(--transition-fast);
z-index: 10;
color: var(--text-secondary);
}
@@ -650,15 +654,12 @@ body {
cursor: not-allowed;
}
.carousel-arrow-left {
left: -24px;
}
.carousel-arrow-right {
right: -24px;
}
@media (max-width: 767px) {
.carousel-wrapper {
flex-direction: column;
gap: var(--space-3);
}
.carousel-arrow {
display: none;
}
@@ -699,14 +700,32 @@ body {
}
/* ===== SELECTED STREAM INFO ===== */
.stream-selection-container {
margin-bottom: var(--space-6);
}
.selected-stream-info {
padding: var(--space-6);
background: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: 8px;
margin-bottom: var(--space-4);
position: relative;
}
.selected-stream-info:last-child {
margin-bottom: var(--space-6);
}
.stream-label {
font-size: var(--text-xs);
font-weight: 600;
color: var(--text-tertiary);
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: var(--space-3);
}
.selected-type {
font-size: var(--text-sm);
font-weight: 600;
@@ -723,6 +742,28 @@ body {
word-break: break-all;
}
.sub-stream {
border-color: rgba(139, 92, 246, 0.3);
}
.btn-remove-sub {
margin-top: var(--space-4);
padding: var(--space-2) var(--space-4);
background: transparent;
border: 1px solid var(--error);
border-radius: 6px;
color: var(--error);
font-size: var(--text-sm);
font-weight: 500;
cursor: pointer;
transition: all var(--transition-fast);
}
.btn-remove-sub:hover {
background: var(--error);
color: white;
}
/* ===== TABS ===== */
.tabs {
margin-bottom: var(--space-6);
@@ -796,13 +837,32 @@ body {
.actions {
display: flex;
gap: var(--space-3);
margin-bottom: var(--space-6);
margin-top: 10px;
margin-bottom: var(--space-4);
}
.actions .btn {
flex: 1;
}
.secondary-actions {
display: flex;
gap: var(--space-3);
margin-bottom: var(--space-6);
}
.secondary-actions .btn {
flex: 1;
}
.secondary-actions .btn-primary {
flex: 1.2;
}
.secondary-actions .btn-outline {
flex: 0.8;
}
/* ===== TOAST ===== */
.toast {
position: fixed;