Backport pull request #4263 from jellyfin/release-10.8.z
Fix change audio track Original-merge: 9139153d16e39c40d068435fe642925a2d7a66a3 Merged-by: Bill Thornton <thornbill@users.noreply.github.com> Backported-by: Joshua M. Boniface <joshua@boniface.me>
This commit is contained in:
parent
5e6de2d7db
commit
abed235b50
4 changed files with 84 additions and 42 deletions
|
@ -11,6 +11,7 @@ import { appHost } from '../apphost';
|
||||||
import Screenfull from 'screenfull';
|
import Screenfull from 'screenfull';
|
||||||
import ServerConnections from '../ServerConnections';
|
import ServerConnections from '../ServerConnections';
|
||||||
import alert from '../alert';
|
import alert from '../alert';
|
||||||
|
import { includesAny } from '../../utils/container.ts';
|
||||||
|
|
||||||
const UNLIMITED_ITEMS = -1;
|
const UNLIMITED_ITEMS = -1;
|
||||||
|
|
||||||
|
@ -1287,6 +1288,7 @@ class PlaybackManager {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const container = mediaSource.Container.toLowerCase();
|
||||||
const codec = (mediaStream.Codec || '').toLowerCase();
|
const codec = (mediaStream.Codec || '').toLowerCase();
|
||||||
|
|
||||||
if (!codec) {
|
if (!codec) {
|
||||||
|
@ -1295,22 +1297,11 @@ class PlaybackManager {
|
||||||
|
|
||||||
const profiles = deviceProfile.DirectPlayProfiles || [];
|
const profiles = deviceProfile.DirectPlayProfiles || [];
|
||||||
|
|
||||||
return profiles.filter(function (p) {
|
return profiles.some(function (p) {
|
||||||
if (p.Type === 'Video') {
|
return p.Type === 'Video'
|
||||||
if (!p.AudioCodec) {
|
&& includesAny((p.Container || '').toLowerCase(), container)
|
||||||
return true;
|
&& includesAny((p.AudioCodec || '').toLowerCase(), codec);
|
||||||
}
|
});
|
||||||
|
|
||||||
// This is an exclusion filter
|
|
||||||
if (p.AudioCodec.indexOf('-') === 0) {
|
|
||||||
return p.AudioCodec.toLowerCase().indexOf(codec) === -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return p.AudioCodec.toLowerCase().indexOf(codec) !== -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}).length > 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.setAudioStreamIndex = function (index, player) {
|
self.setAudioStreamIndex = function (index, player) {
|
||||||
|
|
|
@ -27,10 +27,11 @@ import itemHelper from '../../components/itemHelper';
|
||||||
import Screenfull from 'screenfull';
|
import Screenfull from 'screenfull';
|
||||||
import globalize from '../../scripts/globalize';
|
import globalize from '../../scripts/globalize';
|
||||||
import ServerConnections from '../../components/ServerConnections';
|
import ServerConnections from '../../components/ServerConnections';
|
||||||
import profileBuilder from '../../scripts/browserDeviceProfile';
|
import profileBuilder, { canPlaySecondaryAudio } from '../../scripts/browserDeviceProfile';
|
||||||
import { getIncludeCorsCredentials } from '../../scripts/settings/webSettings';
|
import { getIncludeCorsCredentials } from '../../scripts/settings/webSettings';
|
||||||
import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../components/backdrop/backdrop';
|
import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../components/backdrop/backdrop';
|
||||||
import Events from '../../utils/events.ts';
|
import Events from '../../utils/events.ts';
|
||||||
|
import { includesAny } from '../../utils/container.ts';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns resolved URL.
|
* Returns resolved URL.
|
||||||
|
@ -593,7 +594,7 @@ function tryRemoveElement(elem) {
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
isAudioStreamSupported(stream, deviceProfile) {
|
isAudioStreamSupported(stream, deviceProfile, container) {
|
||||||
const codec = (stream.Codec || '').toLowerCase();
|
const codec = (stream.Codec || '').toLowerCase();
|
||||||
|
|
||||||
if (!codec) {
|
if (!codec) {
|
||||||
|
@ -607,17 +608,11 @@ function tryRemoveElement(elem) {
|
||||||
|
|
||||||
const profiles = deviceProfile.DirectPlayProfiles || [];
|
const profiles = deviceProfile.DirectPlayProfiles || [];
|
||||||
|
|
||||||
return profiles.filter(function (p) {
|
return profiles.some(function (p) {
|
||||||
if (p.Type === 'Video') {
|
return p.Type === 'Video'
|
||||||
if (!p.AudioCodec) {
|
&& includesAny((p.Container || '').toLowerCase(), container)
|
||||||
return true;
|
&& includesAny((p.AudioCodec || '').toLowerCase(), codec);
|
||||||
}
|
});
|
||||||
|
|
||||||
return p.AudioCodec.toLowerCase().includes(codec);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}).length > 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -626,8 +621,11 @@ function tryRemoveElement(elem) {
|
||||||
getSupportedAudioStreams() {
|
getSupportedAudioStreams() {
|
||||||
const profile = this.#lastProfile;
|
const profile = this.#lastProfile;
|
||||||
|
|
||||||
return getMediaStreamAudioTracks(this._currentPlayOptions.mediaSource).filter((stream) => {
|
const mediaSource = this._currentPlayOptions.mediaSource;
|
||||||
return this.isAudioStreamSupported(stream, profile);
|
const container = mediaSource.Container.toLowerCase();
|
||||||
|
|
||||||
|
return getMediaStreamAudioTracks(mediaSource).filter((stream) => {
|
||||||
|
return this.isAudioStreamSupported(stream, profile, container);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1545,12 +1543,12 @@ function tryRemoveElement(elem) {
|
||||||
}
|
}
|
||||||
|
|
||||||
canSetAudioStreamIndex() {
|
canSetAudioStreamIndex() {
|
||||||
if (browser.tizen || browser.orsay) {
|
const video = this.#mediaElement;
|
||||||
return true;
|
if (video) {
|
||||||
|
return canPlaySecondaryAudio(video);
|
||||||
}
|
}
|
||||||
|
|
||||||
const video = this.#mediaElement;
|
return false;
|
||||||
return !!video?.audioTracks;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static onPictureInPictureError(err) {
|
static onPictureInPictureError(err) {
|
||||||
|
|
|
@ -334,6 +334,23 @@ import browser from './browser';
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the web engine supports secondary audio.
|
||||||
|
* @param {HTMLVideoElement} videoTestElement The video test element
|
||||||
|
* @returns {boolean} _true_ if the web engine supports secondary audio.
|
||||||
|
*/
|
||||||
|
export function canPlaySecondaryAudio(videoTestElement) {
|
||||||
|
// We rely on HTMLMediaElement.audioTracks
|
||||||
|
// It works in Chrome 79+ with "Experimental Web Platform features" enabled
|
||||||
|
return !!videoTestElement.audioTracks
|
||||||
|
// It doesn't work in Firefox 108 even with "media.track.enabled" enabled (it only sees the first audio track)
|
||||||
|
&& !browser.firefox
|
||||||
|
// It seems to work on Tizen 5.5+ (2020, Chrome 69+). See https://developer.tizen.org/forums/web-application-development/video-tag-not-work-audiotracks
|
||||||
|
&& (browser.tizenVersion >= 5.5 || !browser.tizen)
|
||||||
|
// Assume webOS 5+ (2020, Chrome 68+) supports secondary audio like Tizen 5.5+
|
||||||
|
&& (browser.web0sVersion >= 5.0 || !browser.web0sVersion);
|
||||||
|
}
|
||||||
|
|
||||||
export default function (options) {
|
export default function (options) {
|
||||||
options = options || {};
|
options = options || {};
|
||||||
|
|
||||||
|
@ -742,13 +759,7 @@ import browser from './browser';
|
||||||
|
|
||||||
profile.CodecProfiles = [];
|
profile.CodecProfiles = [];
|
||||||
|
|
||||||
// We rely on HTMLMediaElement.audioTracks
|
const supportsSecondaryAudio = canPlaySecondaryAudio(videoTestElement);
|
||||||
// It works in Chrome 79+ with "Experimental Web Platform features" enabled
|
|
||||||
// It doesn't work in Firefox 108 even with "media.track.enabled" enabled (it only sees the first audio track)
|
|
||||||
// It seems to work on Tizen 5.5+ (Chrome 69+). See https://developer.tizen.org/forums/web-application-development/video-tag-not-work-audiotracks
|
|
||||||
const supportsSecondaryAudio = !!videoTestElement.audioTracks
|
|
||||||
&& !browser.firefox
|
|
||||||
&& (browser.tizenVersion >= 5.5 || !browser.tizen);
|
|
||||||
|
|
||||||
const aacCodecProfileConditions = [];
|
const aacCodecProfileConditions = [];
|
||||||
|
|
||||||
|
|
42
src/utils/container.ts
Normal file
42
src/utils/container.ts
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
/**
|
||||||
|
* Checks if the list includes any value from the search.
|
||||||
|
* @param list The list to search in.
|
||||||
|
* @param search The values to search.
|
||||||
|
* @returns _true_ if the list includes any value from the search.
|
||||||
|
* @remarks The list (string) can start with '-', in which case the logic is inverted.
|
||||||
|
*/
|
||||||
|
export function includesAny(list: string | string[] | null | undefined, search: string | string[]): boolean {
|
||||||
|
if (!list) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let inverseMatch = false;
|
||||||
|
|
||||||
|
if (typeof list === 'string') {
|
||||||
|
if (list.startsWith('-')) {
|
||||||
|
inverseMatch = true;
|
||||||
|
list = list.substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
list = list.split(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
list = list.filter(i => i);
|
||||||
|
|
||||||
|
if (!list.length) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof search === 'string') {
|
||||||
|
search = search.split(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
search = search.filter(i => i);
|
||||||
|
|
||||||
|
/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */
|
||||||
|
if (search.some(s => list!.includes(s))) {
|
||||||
|
return !inverseMatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
return inverseMatch;
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue