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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
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
r.ParseForm()
startTime := r.Form.Get("start")
requestedSize := r.Form.Get("resolution")
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.StartTime = startTime
options.MaxTranscodeSize = config.GetMaxStreamingTranscodeSize()
if requestedSize != "" {
options.MaxTranscodeSize = models.StreamingResolutionEnum(requestedSize)
}
encoder := ffmpeg.NewEncoder(manager.GetInstance().FFMPEGPath)
stream, err = encoder.GetTranscodeStream(options)

View File

@ -67,7 +67,7 @@ var CodecH264 = Codec{
format: "mp4",
MimeType: MimeMp4,
extraArgs: []string{
"-movflags", "frag_keyframe",
"-movflags", "frag_keyframe+empty_moov",
"-pix_fmt", "yuv420p",
"-preset", "veryfast",
"-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{
{
URL: directStreamURL + ".webm",
MimeType: &mimeWebm,
Label: &labelWebm,
},
{
URL: directStreamURL + ".m3u8",
MimeType: &mimeHLS,
Label: &labelHLS,
},
}
ret = append(ret, defaultStreams...)
// TODO - at some point, look at streaming at various resolutions
return ret, nil
}

View File

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

View File

@ -32,13 +32,8 @@ export class ScenePlayerImpl extends React.Component<
return false;
}
const startIndex = src.lastIndexOf("?start=");
let srcCopy = src;
if (startIndex !== -1) {
srcCopy = srcCopy.substring(0, startIndex);
}
return srcCopy.endsWith("/stream");
const url = new URL(src);
return url.pathname.endsWith("/stream");
}
// Typings for jwplayer are, unfortunately, very lacking
@ -59,6 +54,9 @@ export class ScenePlayerImpl extends React.Component<
scrubberPosition: 0,
config: this.makeJWPlayerConfig(props.scene),
};
// Default back to Direct Streaming
localStorage.removeItem("jwplayer.qualityLabel");
}
public UNSAFE_componentWillReceiveProps(props: IScenePlayerProps) {
@ -98,18 +96,27 @@ export class ScenePlayerImpl extends React.Component<
this.player.on("error", (err: any) => {
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) => {
if (
metadata.metadataType === "media" &&
!metadata.width &&
!metadata.height
) {
// treat this as a decoding error and try the next source
this.handleError();
// Occurs during preload when videos with supported audio/unsupported video are preloaded.
// 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();
}
private handleError() {
private handleError(play: boolean) {
const currentFile = this.player.getPlaylistItem();
if (currentFile) {
// eslint-disable-next-line no-console
@ -152,8 +159,13 @@ export class ScenePlayerImpl extends React.Component<
if (this.tryNextStream()) {
// 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);
if (play) {
this.player.play();
}
}
}
@ -211,28 +223,30 @@ export class ScenePlayerImpl extends React.Component<
};
const seekHook = (seekToPosition: number, _videoTag: HTMLVideoElement) => {
if (
!_videoTag.src ||
ScenePlayerImpl.isDirectStream(_videoTag.src) ||
_videoTag.src.endsWith(".m3u8")
) {
if (!_videoTag.src || _videoTag.src.endsWith(".m3u8")) {
return false;
}
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
return false;
}
// remove the start parameter
let { src } = _videoTag;
const startIndex = src.lastIndexOf("?start=");
if (startIndex !== -1) {
src = src.substring(0, startIndex);
}
const srcUrl = new URL(_videoTag.src);
srcUrl.searchParams.delete("start");
/* eslint-disable no-param-reassign */
_videoTag.dataset.start = seekToPosition.toString();
_videoTag.src = `${src}?start=${seekToPosition}`;
srcUrl.searchParams.append("start", seekToPosition.toString());
_videoTag.src = srcUrl.toString();
/* eslint-enable no-param-reassign */
_videoTag.play();
// return true to indicate not to fall through to default