Added streaming quality options (#790)

This commit is contained in:
JoeSmithStarkers
2020-10-22 15:02:27 +11:00
committed by GitHub
parent a31c8ccd33
commit 71c814c116
5 changed files with 157 additions and 33 deletions

View File

@@ -145,6 +145,7 @@ func (rs sceneRoutes) streamTranscode(w http.ResponseWriter, r *http.Request, vi
// start stream based on query param, if provided // start stream based on query param, if provided
r.ParseForm() r.ParseForm()
startTime := r.Form.Get("start") startTime := r.Form.Get("start")
requestedSize := r.Form.Get("resolution")
var stream *ffmpeg.Stream var stream *ffmpeg.Stream
@@ -156,6 +157,9 @@ func (rs sceneRoutes) streamTranscode(w http.ResponseWriter, r *http.Request, vi
options := ffmpeg.GetTranscodeStreamOptions(*videoFile, videoCodec, audioCodec) options := ffmpeg.GetTranscodeStreamOptions(*videoFile, videoCodec, audioCodec)
options.StartTime = startTime options.StartTime = startTime
options.MaxTranscodeSize = config.GetMaxStreamingTranscodeSize() options.MaxTranscodeSize = config.GetMaxStreamingTranscodeSize()
if requestedSize != "" {
options.MaxTranscodeSize = models.StreamingResolutionEnum(requestedSize)
}
encoder := ffmpeg.NewEncoder(manager.GetInstance().FFMPEGPath) encoder := ffmpeg.NewEncoder(manager.GetInstance().FFMPEGPath)
stream, err = encoder.GetTranscodeStream(options) stream, err = encoder.GetTranscodeStream(options)

View File

@@ -67,7 +67,7 @@ var CodecH264 = Codec{
format: "mp4", format: "mp4",
MimeType: MimeMp4, MimeType: MimeMp4,
extraArgs: []string{ extraArgs: []string{
"-movflags", "frag_keyframe", "-movflags", "frag_keyframe+empty_moov",
"-pix_fmt", "yuv420p", "-pix_fmt", "yuv420p",
"-preset", "veryfast", "-preset", "veryfast",
"-crf", "25", "-crf", "25",

View File

@@ -228,23 +228,128 @@ func GetSceneStreamPaths(scene *models.Scene, directStreamURL string) ([]*models
}) })
} }
hls := models.SceneStreamEndpoint{
URL: directStreamURL + ".m3u8",
MimeType: &mimeHLS,
Label: &labelHLS,
}
ret = append(ret, &hls)
// WEBM quality transcoding options
// Note: These have the wrong mime type intentionally to allow jwplayer to selection between mp4/webm
webmLabelFourK := "WEBM 4K (2160p)" // "FOUR_K"
webmLabelFullHD := "WEBM Full HD (1080p)" // "FULL_HD"
webmLabelStardardHD := "WEBM HD (720p)" // "STANDARD_HD"
webmLabelStandard := "WEBM Standard (480p)" // "STANDARD"
webmLabelLow := "WEBM Low (240p)" // "LOW"
if !scene.Height.Valid || scene.Height.Int64 >= 2160 {
new := models.SceneStreamEndpoint{
URL: directStreamURL + ".webm?resolution=FOUR_K",
MimeType: &mimeMp4,
Label: &webmLabelFourK,
}
ret = append(ret, &new)
}
if !scene.Height.Valid || scene.Height.Int64 >= 1080 {
new := models.SceneStreamEndpoint{
URL: directStreamURL + ".webm?resolution=FULL_HD",
MimeType: &mimeMp4,
Label: &webmLabelFullHD,
}
ret = append(ret, &new)
}
if !scene.Height.Valid || scene.Height.Int64 >= 720 {
new := models.SceneStreamEndpoint{
URL: directStreamURL + ".webm?resolution=STANDARD_HD",
MimeType: &mimeMp4,
Label: &webmLabelStardardHD,
}
ret = append(ret, &new)
}
if !scene.Height.Valid || scene.Height.Int64 >= 480 {
new := models.SceneStreamEndpoint{
URL: directStreamURL + ".webm?resolution=STANDARD",
MimeType: &mimeMp4,
Label: &webmLabelStandard,
}
ret = append(ret, &new)
}
if !scene.Height.Valid || scene.Height.Int64 >= 240 {
new := models.SceneStreamEndpoint{
URL: directStreamURL + ".webm?resolution=LOW",
MimeType: &mimeMp4,
Label: &webmLabelLow,
}
ret = append(ret, &new)
}
// Setup up lower quality transcoding options (MP4)
mp4LabelFourK := "MP4 4K (2160p)" // "FOUR_K"
mp4LabelFullHD := "MP4 Full HD (1080p)" // "FULL_HD"
mp4LabelStardardHD := "MP4 HD (720p)" // "STANDARD_HD"
mp4LabelStandard := "MP4 Standard (480p)" // "STANDARD"
mp4LabelLow := "MP4 Low (240p)" // "LOW"
if !scene.Height.Valid || scene.Height.Int64 >= 2160 {
new := models.SceneStreamEndpoint{
URL: directStreamURL + ".mp4?resolution=FOUR_K",
MimeType: &mimeMp4,
Label: &mp4LabelFourK,
}
ret = append(ret, &new)
}
if !scene.Height.Valid || scene.Height.Int64 >= 1080 {
new := models.SceneStreamEndpoint{
URL: directStreamURL + ".mp4?resolution=FULL_HD",
MimeType: &mimeMp4,
Label: &mp4LabelFullHD,
}
ret = append(ret, &new)
}
if !scene.Height.Valid || scene.Height.Int64 >= 720 {
new := models.SceneStreamEndpoint{
URL: directStreamURL + ".mp4?resolution=STANDARD_HD",
MimeType: &mimeMp4,
Label: &mp4LabelStardardHD,
}
ret = append(ret, &new)
}
if !scene.Height.Valid || scene.Height.Int64 >= 480 {
new := models.SceneStreamEndpoint{
URL: directStreamURL + ".mp4?resolution=STANDARD",
MimeType: &mimeMp4,
Label: &mp4LabelStandard,
}
ret = append(ret, &new)
}
if !scene.Height.Valid || scene.Height.Int64 >= 240 {
new := models.SceneStreamEndpoint{
URL: directStreamURL + ".mp4?resolution=LOW",
MimeType: &mimeMp4,
Label: &mp4LabelLow,
}
ret = append(ret, &new)
}
defaultStreams := []*models.SceneStreamEndpoint{ defaultStreams := []*models.SceneStreamEndpoint{
{ {
URL: directStreamURL + ".webm", URL: directStreamURL + ".webm",
MimeType: &mimeWebm, MimeType: &mimeWebm,
Label: &labelWebm, Label: &labelWebm,
}, },
{
URL: directStreamURL + ".m3u8",
MimeType: &mimeHLS,
Label: &labelHLS,
},
} }
ret = append(ret, defaultStreams...) ret = append(ret, defaultStreams...)
// TODO - at some point, look at streaming at various resolutions
return ret, nil return ret, nil
} }

View File

@@ -1,4 +1,5 @@
### ✨ New Features ### ✨ New Features
* Add selectable streaming quality profiles in the scene player.
* Add scrapers list setting page. * Add scrapers list setting page.
* Add support for individual images and manual creation of galleries. * Add support for individual images and manual creation of galleries.
* Add various fields to galleries. * Add various fields to galleries.

View File

@@ -32,13 +32,8 @@ export class ScenePlayerImpl extends React.Component<
return false; return false;
} }
const startIndex = src.lastIndexOf("?start="); const url = new URL(src);
let srcCopy = src; return url.pathname.endsWith("/stream");
if (startIndex !== -1) {
srcCopy = srcCopy.substring(0, startIndex);
}
return srcCopy.endsWith("/stream");
} }
// Typings for jwplayer are, unfortunately, very lacking // Typings for jwplayer are, unfortunately, very lacking
@@ -59,6 +54,9 @@ export class ScenePlayerImpl extends React.Component<
scrubberPosition: 0, scrubberPosition: 0,
config: this.makeJWPlayerConfig(props.scene), config: this.makeJWPlayerConfig(props.scene),
}; };
// Default back to Direct Streaming
localStorage.removeItem("jwplayer.qualityLabel");
} }
public UNSAFE_componentWillReceiveProps(props: IScenePlayerProps) { public UNSAFE_componentWillReceiveProps(props: IScenePlayerProps) {
@@ -98,18 +96,27 @@ export class ScenePlayerImpl extends React.Component<
this.player.on("error", (err: any) => { this.player.on("error", (err: any) => {
if (err && err.code === 224003) { if (err && err.code === 224003) {
this.handleError(); // When jwplayer has been requested to play but the browser doesn't support the video format.
this.handleError(true);
} }
}); });
//
this.player.on("meta", (metadata: any) => { this.player.on("meta", (metadata: any) => {
if ( if (
metadata.metadataType === "media" && metadata.metadataType === "media" &&
!metadata.width && !metadata.width &&
!metadata.height !metadata.height
) { ) {
// treat this as a decoding error and try the next source // Occurs during preload when videos with supported audio/unsupported video are preloaded.
this.handleError(); // Treat this as a decoding error and try the next source without playing.
// However on Safari we get an media event when m3u8 is loaded which needs to be ignored.
const currentFile = this.player.getPlaylistItem().file;
if (currentFile != null && !currentFile.includes("m3u8")) {
const state = this.player.getState();
const play = state === "buffering" || state === "playing";
this.handleError(play);
}
} }
}); });
@@ -143,7 +150,7 @@ export class ScenePlayerImpl extends React.Component<
this.player.pause(); this.player.pause();
} }
private handleError() { private handleError(play: boolean) {
const currentFile = this.player.getPlaylistItem(); const currentFile = this.player.getPlaylistItem();
if (currentFile) { if (currentFile) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
@@ -152,8 +159,13 @@ export class ScenePlayerImpl extends React.Component<
if (this.tryNextStream()) { if (this.tryNextStream()) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log("Trying next source in playlist"); console.log(
`Trying next source in playlist: ${this.playlist.sources[0].file}`
);
this.player.load(this.playlist); this.player.load(this.playlist);
if (play) {
this.player.play();
}
} }
} }
@@ -211,28 +223,30 @@ export class ScenePlayerImpl extends React.Component<
}; };
const seekHook = (seekToPosition: number, _videoTag: HTMLVideoElement) => { const seekHook = (seekToPosition: number, _videoTag: HTMLVideoElement) => {
if ( if (!_videoTag.src || _videoTag.src.endsWith(".m3u8")) {
!_videoTag.src || return false;
ScenePlayerImpl.isDirectStream(_videoTag.src) || }
_videoTag.src.endsWith(".m3u8")
) { if (ScenePlayerImpl.isDirectStream(_videoTag.src)) {
if (_videoTag.dataset.start) {
/* eslint-disable-next-line no-param-reassign */
_videoTag.dataset.start = "0";
}
// direct stream - fall back to default // direct stream - fall back to default
return false; return false;
} }
// remove the start parameter // remove the start parameter
let { src } = _videoTag; const srcUrl = new URL(_videoTag.src);
srcUrl.searchParams.delete("start");
const startIndex = src.lastIndexOf("?start=");
if (startIndex !== -1) {
src = src.substring(0, startIndex);
}
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
_videoTag.dataset.start = seekToPosition.toString(); _videoTag.dataset.start = seekToPosition.toString();
srcUrl.searchParams.append("start", seekToPosition.toString());
_videoTag.src = `${src}?start=${seekToPosition}`; _videoTag.src = srcUrl.toString();
/* eslint-enable no-param-reassign */ /* eslint-enable no-param-reassign */
_videoTag.play(); _videoTag.play();
// return true to indicate not to fall through to default // return true to indicate not to fall through to default