Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 29 additions & 2 deletions core/frontend/src/components/video-manager/VideoDevice.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@
mdi-circle
</v-icon>
</template>
<span v-if="!are_video_streams_available">
<span v-if="device.blocked">
Video source is blocked
</span>
<span v-else-if="!are_video_streams_available">
No streams added to this video source
</span>
<span v-else-if="has_healthy_streams">
Expand Down Expand Up @@ -43,12 +46,21 @@
</v-btn>
<v-btn
class="my-1"
:disabled="!is_redirect_source && (are_video_streams_available || updating_streams)"
:disabled="device.blocked || (!is_redirect_source && (are_video_streams_available || updating_streams))"
@click="openStreamCreationDialog"
>
<v-icon>mdi-plus</v-icon>
Add stream
</v-btn>
<v-switch
v-if="is_pirate_mode"
:input-value="device.blocked"
dense
hide-details
class="mt-2"
label="Block source"
@change="toggleBlocked"
Comment on lines +55 to +62
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Consider handling transient toggle state or disabling the switch while the block/unblock request is in flight.

As written, users can toggle the switch multiple times while toggleBlocked is still waiting on the backend, which can trigger overlapping block/unblock calls and leave the UI out of sync. A simple blocking/unblocking flag wired to :loading/:disabled on the v-switch (or reusing updating_streams if it fits) would prevent this and make the interaction more reliable.

Suggested implementation:

          <v-switch
            v-if="is_pirate_mode"
            :input-value="device.blocked"
            :disabled="updating_streams"
            :loading="updating_streams"
            dense
            hide-details
            class="mt-2"
            label="Block source"
            @change="toggleBlocked"
          />

To fully address the race condition, ensure that toggleBlocked manages updating_streams (or a dedicated flag like blocking_source) while the block/unblock request is in flight, for example:

  1. Set this.updating_streams = true before issuing the backend request.
  2. Use a try/finally block so that this.updating_streams is reset to false regardless of success/failure.
  3. Optionally, handle failures by reverting the local device.blocked state or re-fetching device data so the UI stays in sync with the backend.

If updating_streams is not appropriate for this purpose, introduce a new blocking_source data property and bind :disabled / :loading to that instead, updating it in toggleBlocked with the same pattern.

/>
</div>
</div>
<div>
Expand Down Expand Up @@ -104,6 +116,7 @@
import Vue, { PropType } from 'vue'

import SpinningLogo from '@/components/common/SpinningLogo.vue'
import settings from '@/libs/settings'
import video from '@/store/video'
import {
CreatedStream, Device, StreamStatus,
Expand Down Expand Up @@ -160,7 +173,13 @@ export default Vue.extend({
(stream) => stream.video_and_stream.stream_information?.extended_configuration?.disable_thumbnails === true,
)
},
is_pirate_mode(): boolean {
return settings.is_pirate_mode
},
status_color(): string {
if (this.device.blocked) {
return 'warning'
}
if (!this.are_video_streams_available) {
return 'grey'
}
Expand All @@ -178,6 +197,14 @@ export default Vue.extend({
this.show_stream_creation_dialog = true
},

async toggleBlocked(): Promise<void> {
if (this.device.blocked) {
await video.unblockSource(this.device.source)
} else {
await video.blockSource(this.device.source)
}
},

async createNewStream(stream: CreatedStream): Promise<void> {
await video.createStream(stream)
},
Expand Down
36 changes: 36 additions & 0 deletions core/frontend/src/store/video.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,42 @@ class VideoStore extends VuexModule {
}
}

@Action
async blockSource(source: string): Promise<void> {
await back_axios({
method: 'post',
url: `${this.API_URL}/block_source`,
timeout: 10000,
params: { source_string: source },
})
.then(() => {
this.fetchDevices()
this.fetchStreams()
})
.catch((error) => {
const message = `Could not block video source: ${error.message}.`
notifier.pushError('VIDEO_SOURCE_BLOCK_FAIL', message)
})
}
Comment on lines +248 to +264
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Avoid mixing async/await with .then/.catch and consider awaiting the subsequent fetches.

Given this is already an async function using await, prefer a try/catch instead of .then/.catch for consistency and readability. That also lets you await Promise.all([this.fetchDevices(), this.fetchStreams()]) so blockSource only resolves once the device and stream state are updated for callers.

Suggested change
@Action
async blockSource(source: string): Promise<void> {
await back_axios({
method: 'post',
url: `${this.API_URL}/block_source`,
timeout: 10000,
params: { source_string: source },
})
.then(() => {
this.fetchDevices()
this.fetchStreams()
})
.catch((error) => {
const message = `Could not block video source: ${error.message}.`
notifier.pushError('VIDEO_SOURCE_BLOCK_FAIL', message)
})
}
@Action
async blockSource(source: string): Promise<void> {
try {
await back_axios({
method: 'post',
url: `${this.API_URL}/block_source`,
timeout: 10000,
params: { source_string: source },
})
await Promise.all([this.fetchDevices(), this.fetchStreams()])
} catch (error: any) {
const message = `Could not block video source: ${error.message}.`
notifier.pushError('VIDEO_SOURCE_BLOCK_FAIL', message)
}
}


@Action
async unblockSource(source: string): Promise<void> {
await back_axios({
method: 'post',
url: `${this.API_URL}/unblock_source`,
timeout: 10000,
params: { source_string: source },
})
.then(() => {
this.fetchDevices()
this.fetchStreams()
})
.catch((error) => {
const message = `Could not unblock video source: ${error.message}.`
notifier.pushError('VIDEO_SOURCE_UNBLOCK_FAIL', message)
})
}
Comment on lines +266 to +282
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Mirror the blockSource refactor here for consistency and clearer control flow.

Please apply the same structure you use in blockSource (e.g., try/catch with awaited follow-up fetches) so these two actions stay in sync and avoid subtle behavioral differences between block and unblock over time.

Suggested change
@Action
async unblockSource(source: string): Promise<void> {
await back_axios({
method: 'post',
url: `${this.API_URL}/unblock_source`,
timeout: 10000,
params: { source_string: source },
})
.then(() => {
this.fetchDevices()
this.fetchStreams()
})
.catch((error) => {
const message = `Could not unblock video source: ${error.message}.`
notifier.pushError('VIDEO_SOURCE_UNBLOCK_FAIL', message)
})
}
@Action
async unblockSource(source: string): Promise<void> {
try {
await back_axios({
method: 'post',
url: `${this.API_URL}/unblock_source`,
timeout: 10000,
params: { source_string: source },
})
await this.fetchDevices()
await this.fetchStreams()
} catch (error: any) {
const message = `Could not unblock video source: ${error.message}.`
notifier.pushError('VIDEO_SOURCE_UNBLOCK_FAIL', message)
}
}


@Action
async resetSettings(): Promise<void> {
await back_axios({
Expand Down
1 change: 1 addition & 0 deletions core/frontend/src/types/video.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ export interface Device {
source: string
formats: Format[]
controls: Control[]
blocked?: boolean
}

export enum VideoCaptureType {
Expand Down
Loading