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
2 changes: 2 additions & 0 deletions graphql/schema/types/scene.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,8 @@ type SceneStreamEndpoint {
url: String!
mime_type: String
label: String
stream_type: String
resolution: String
}

input AssignSceneFileInput {
Expand Down
31 changes: 22 additions & 9 deletions internal/manager/scene.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,48 +11,57 @@ import (
)

type SceneStreamEndpoint struct {
URL string `json:"url"`
MimeType *string `json:"mime_type"`
Label *string `json:"label"`
URL string `json:"url"`
MimeType *string `json:"mime_type"`
Label *string `json:"label"`
StreamType *string `json:"stream_type"`
Resolution *string `json:"resolution"`
}

type endpointType struct {
label string
mimeType string
extension string
label string
mimeType string
extension string
streamType string
}

var (
directEndpointType = endpointType{
label: "Direct stream",
mimeType: ffmpeg.MimeMp4Video,
extension: "",
streamType: "direct",
}
mp4EndpointType = endpointType{
label: "MP4",
mimeType: ffmpeg.MimeMp4Video,
extension: ".mp4",
streamType: "mp4",
}
mkvEndpointType = endpointType{
label: "MKV",
// use mp4 mimetype to trick the client, since many clients won't try mkv
mimeType: ffmpeg.MimeMp4Video,
extension: ".mkv",
streamType: "mkv",
}
webmEndpointType = endpointType{
label: "WEBM",
mimeType: ffmpeg.MimeWebmVideo,
extension: ".webm",
streamType: "webm",
}
hlsEndpointType = endpointType{
label: "HLS",
mimeType: ffmpeg.MimeHLS,
extension: ".m3u8",
streamType: "hls",
}
dashEndpointType = endpointType{
label: "DASH",
mimeType: ffmpeg.MimeDASH,
extension: ".mpd",
streamType: "dash",
}
)

Expand Down Expand Up @@ -117,8 +126,10 @@ func GetSceneStreamPaths(scene *models.Scene, directStreamURL *url.URL, maxStrea
url.Path += t.extension

label := t.label
resolutionStr := "ORIGINAL"

if resolution != "" {
resolutionStr = string(resolution)
v := url.Query()
v.Set("resolution", resolution.String())
url.RawQuery = v.Encode()
Expand All @@ -138,9 +149,11 @@ func GetSceneStreamPaths(scene *models.Scene, directStreamURL *url.URL, maxStrea
}

return &SceneStreamEndpoint{
URL: url.String(),
MimeType: &t.mimeType,
Label: &label,
URL: url.String(),
MimeType: &t.mimeType,
Label: &label,
StreamType: &t.streamType,
Resolution: &resolutionStr,
}
}

Expand Down
2 changes: 2 additions & 0 deletions ui/v2.5/graphql/data/scene.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ fragment SceneData on Scene {
url
mime_type
label
stream_type
resolution
}

custom_fields
Expand Down
2 changes: 2 additions & 0 deletions ui/v2.5/graphql/queries/scene.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ query SceneStreams($id: ID!) {
url
mime_type
label
stream_type
resolution
}
}
}
Expand Down
8 changes: 7 additions & 1 deletion ui/v2.5/src/components/ScenePlayer/ScenePlayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -639,10 +639,14 @@ export const ScenePlayer: React.FC<IScenePlayerProps> = PatchComponent(
src: stream.url,
type: stream.mime_type ?? undefined,
label: stream.label ?? undefined,
streamType: stream.stream_type ?? undefined,
resolution: stream.resolution ?? undefined,
offset: !isDirect(src),
duration,
};
})
}),
uiConfig?.defaultStreamType,
uiConfig?.defaultStreamingResolution
);

function getDefaultLanguageCode() {
Expand Down Expand Up @@ -733,6 +737,8 @@ export const ScenePlayer: React.FC<IScenePlayerProps> = PatchComponent(
interfaceConfig?.autostartVideo,
uiConfig?.alwaysStartFromBeginning,
uiConfig?.disableMobileMediaAutoRotateEnabled,
uiConfig?.defaultStreamType,
uiConfig?.defaultStreamingResolution,
_initialTimestamp,
]);

Expand Down
5 changes: 4 additions & 1 deletion ui/v2.5/src/components/ScenePlayer/live.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,10 @@ function offsetMiddleware(player: VideoJsPlayer) {
tech.trigger("timeupdate");
tech.trigger("pause");
tech.trigger("seeking");
tech.play();
tech.play().catch(() => {
// auto-play failed due to browser restrictions
seeking = 0;
});
},
loadDelay,
{ leading: true }
Expand Down
52 changes: 38 additions & 14 deletions ui/v2.5/src/components/ScenePlayer/source-selector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,23 @@ import videojs, { VideoJsPlayer } from "video.js";
export interface ISource extends videojs.Tech.SourceObject {
label?: string;
errored?: boolean;
streamType?: string;
resolution?: string;
}

function preferredSourceIndex(
sources: ISource[],
preferredType: string | undefined,
preferredResolution: string | undefined
): number {
let matchedType = -1;
for (let i = 0; i < sources.length; i++) {
const source = sources[i];
const matchType = preferredType ? source.streamType === preferredType : true;
if (matchType && source.resolution === preferredResolution) return i;
if (matchType && source.resolution === "ORIGINAL" && matchedType === -1) matchedType = i;
}
return matchedType === -1 ? 0 : matchedType;
}

class SourceMenuItem extends videojs.getComponent("MenuItem") {
Expand Down Expand Up @@ -46,11 +63,11 @@ class SourceMenuButton extends videojs.getComponent("MenuButton") {
});
}

public setSources(sources: ISource[]) {
public setSources(sources: ISource[], selectedIndex: number) {
this.selectedSource = null;

this.items = sources.map((source, i) => {
if (i === 0) {
if (i === selectedIndex) {
this.selectedSource = source;
}

Expand Down Expand Up @@ -176,8 +193,10 @@ class SourceSelectorPlugin extends videojs.getPlugin("plugin") {
const currentSource = player.currentSource() as ISource;
console.log(`Source '${currentSource.label}' is unsupported`);

// mark current source as errored
currentSource.errored = true;
// mark current source as errored in this.sources (currentSource() returns a copy)
if (this.selectedIndex >= 0 && this.selectedIndex < this.sources.length) {
this.sources[this.selectedIndex].errored = true;
}
this.menu.markSourceErrored(currentSource);

// don't auto play next source if user manually selected a source
Expand All @@ -187,12 +206,10 @@ class SourceSelectorPlugin extends videojs.getPlugin("plugin") {

// TODO - make auto play next source configurable
// try the next source in the list
if (
this.selectedIndex !== -1 &&
this.selectedIndex + 1 < this.sources.length
) {
this.selectedIndex += 1;
const newSource = this.sources[this.selectedIndex];
const nextIndex = (this.selectedIndex + 1) % this.sources.length;
if (this.selectedIndex !== -1 && !this.sources[nextIndex].errored) {
this.selectedIndex = nextIndex;
const newSource = this.sources[nextIndex];
console.log(`Trying next source in playlist: '${newSource.label}'`);
this.menu.setSelectedSource(newSource);

Expand All @@ -209,21 +226,28 @@ class SourceSelectorPlugin extends videojs.getPlugin("plugin") {
});
}

setSources(sources: ISource[]) {
setSources(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Changing the default stream type/quality while the current scene remains mounted does not reliably update the selected source. ScenePlayer.tsx passes the new preferences to sourceSelector.setSources, but that code only runs past the scene.id === sceneId.current guard during scene initialization, and the new config fields are not part of a separate reselection path. This means that if you change the setting and refresh a scene page, it breaks and will not load the video to play. It can be resolved by leaving the page and reopening the scene.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Just wanted to note that the issue still exists. Steps to test:

  1. Have a tab playing a scene
  2. on another tab, change the default settings to another type
  3. refresh the first scene tab
  4. Note that it has updated to the new default but the video is black screened and doesnt play

sources: ISource[],
preferredType: string | undefined,
preferredResolution: string | undefined
) {
const cleanupTracks = this.cleanupTextTracks.splice(0);
for (const track of cleanupTracks) {
this.player.removeRemoteTextTrack(track);
}

this.menu.setSources(sources);
const selectedIndex = preferredSourceIndex(sources, preferredType, preferredResolution);
if (selectedIndex === this.selectedIndex) return;

this.menu.setSources(sources, selectedIndex);
if (sources.length !== 0) {
this.selectedIndex = 0;
this.selectedIndex = selectedIndex;
} else {
this.selectedIndex = -1;
}

this.sources = sources;
this.player.src(sources[0]);
this.player.src(sources[selectedIndex]);
}

get textTracks(): HTMLTrackElement[] {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,43 @@ export const SettingsInterfacePanel: React.FC = PatchComponent(
checked={ui.showAbLoopControls ?? undefined}
onChange={(v) => saveUI({ showAbLoopControls: v })}
/>
<SelectSetting
id="default-stream-type"
headingID="config.ui.scene_player.options.default_stream_type.heading"
subHeadingID="config.ui.scene_player.options.default_stream_type.description"
value={ui.defaultStreamType ?? ""}
onChange={(v) => saveUI({ defaultStreamType: v })}
>
<option value="">
{intl.formatMessage({
id: "config.ui.scene_player.options.default_stream_type.no_preference",
})}
</option>
<option value="direct">Direct stream</option>
<option value="mkv">MKV</option>
<option value="mp4">MP4</option>
<option value="webm">WEBM</option>
<option value="hls">HLS</option>
<option value="dash">DASH</option>
</SelectSetting>
<SelectSetting
id="default-streaming-resolution"
headingID="config.ui.scene_player.options.default_streaming_resolution.heading"
subHeadingID="config.ui.scene_player.options.default_streaming_resolution.description"
value={ui.defaultStreamingResolution ?? ""}
onChange={(v) => saveUI({ defaultStreamingResolution: v })}
>
<option value="ORIGINAL">
{intl.formatMessage({
id: "config.ui.scene_player.options.default_streaming_resolution.original",
})}
</option>
<option value="FOUR_K">4K (2160p)</option>
<option value="FULL_HD">Full HD (1080p)</option>
<option value="STANDARD_HD">HD (720p)</option>
<option value="STANDARD">Standard (480p)</option>
<option value="LOW">Low (240p)</option>
</SelectSetting>
</SettingSection>
<SettingSection headingID="config.ui.tag_panel.heading">
<BooleanSetting
Expand Down
4 changes: 4 additions & 0 deletions ui/v2.5/src/core/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ export interface IUIConfig {

showAbLoopControls?: boolean;

// resolution and stream type automatically selected when streaming a scene.
defaultStreamingResolution?: string;
defaultStreamType?: string;

// maximum number of items to shown in the dropdown list - defaults to 200
// upper limit of 1000
maxOptionsShown?: number;
Expand Down
10 changes: 10 additions & 0 deletions ui/v2.5/src/locales/en-GB.json
Original file line number Diff line number Diff line change
Expand Up @@ -851,6 +851,16 @@
"vr_tag": {
"description": "The VR button will only be displayed for scenes with this tag.",
"heading": "VR tag"
},
"default_streaming_resolution": {
"heading": "Default streaming resolution",
"description": "Resolution automatically selected when streaming a scene.",
"original": "Original"
},
"default_stream_type": {
"heading": "Default stream type",
"description": "Media type automatically selected when streaming a scene.",
"no_preference": "No preference"
}
}
},
Expand Down