Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Local API: Use IOS HLS manifest for livestreams #5705

Merged
merged 1 commit into from
Sep 16, 2024
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,17 @@ export default defineComponent({

// #region player config

const seekingIsPossible = computed(() => {
if (props.manifestMimeType !== 'application/x-mpegurl') {
return true
}

const match = props.manifestSrc.match(/\/(?:manifest|playlist)_duration\/(\d+)\//)

// Check how many seconds we are allowed to seek, 30 is too short, 3600 is an hour which is great
return match != null && parseInt(match[1] || '0') > 30
})

/**
* @param {'dash'|'audio'|'legacy'} format
* @param {boolean} useAutoQuality
Expand All @@ -545,7 +556,7 @@ export default defineComponent({
dash: {
manifestPreprocessorTXml: manifestPreprocessorTXml
},
availabilityWindowOverride: props.manifestMimeType === 'application/x-mpegurl' ? 0 : NaN
availabilityWindowOverride: seekingIsPossible.value ? NaN : 0
},
abr: {
enabled: useAutoQuality,
Expand Down Expand Up @@ -794,7 +805,7 @@ export default defineComponent({
function configureUI(firstTime = false) {
if (firstTime) {
const firstTimeConfig = {
addSeekBar: props.manifestMimeType !== 'application/x-mpegurl',
addSeekBar: seekingIsPossible.value,
customContextMenu: true,
contextMenuElements: ['ft_stats'],
enableTooltips: true,
Expand Down Expand Up @@ -1136,15 +1147,29 @@ export default defineComponent({
} else if (type === RequestType.MANIFEST && context.type === AdvancedRequestType.MEDIA_PLAYLIST) {
const url = new URL(response.uri)

let modifiedText

// Fixes proxied HLS manifests, as Invidious replaces the path parameters with query parameters,
// so shaka-player isn't able to infer the mime type from the `/file/seg.ts` part like it does for non-proxied HLS manifests.
// Shaka-player does attempt to detect it with HEAD request but the `Content-Type` header is `application/octet-stream`,
// which still doesn't tell shaka-player how to handle the stream because that's the equivalent of saying "binary data".
if (url.searchParams.has('local')) {
const stringBody = new TextDecoder().decode(response.data)
const fixed = stringBody.replaceAll(/https?:\/\/.+$/gm, hlsProxiedUrlReplacer)

response.data = new TextEncoder().encode(fixed).buffer
modifiedText = stringBody.replaceAll(/https?:\/\/.+$/gm, hlsProxiedUrlReplacer)
}

// The audio-only streams are actually raw AAC, so correct the file extension from `.ts` to `.aac`
if (/\/itag\/23[34]\//.test(url.pathname) || url.searchParams.get('itag') === '233' || url.searchParams.get('itag') === '234') {
if (!modifiedText) {
modifiedText = new TextDecoder().decode(response.data)
}

modifiedText = modifiedText.replaceAll('/file/seg.ts', '/file/seg.aac')
}

if (modifiedText) {
response.data = new TextEncoder().encode(modifiedText).buffer
}
}
}
Expand Down Expand Up @@ -1334,7 +1359,7 @@ export default defineComponent({
stats.bitrate = (newTrack.bandwidth / 1000).toFixed(2)

// Combined audio and video HLS streams
if (newTrack.videoCodec.includes(',')) {
if (newTrack.videoCodec?.includes(',')) {
stats.codecs.audioItag = ''
stats.codecs.videoItag = ''

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ export class AudioTrackSelection extends shaka.ui.SettingsMenu {

this.button.setAttribute('shaka-status', this.currentSelection.innerText)

if (knownLabels.size > 0) {
if (knownLabels.size > 1) {
this.button.classList.remove('shaka-hidden')
} else {
this.button.classList.add('shaka-hidden')
Expand Down
7 changes: 2 additions & 5 deletions src/renderer/helpers/api/local.js
Original file line number Diff line number Diff line change
Expand Up @@ -284,16 +284,13 @@ export async function getLocalVideoInfo(id) {
info.storyboards = iosInfo.storyboards
} else if (iosInfo.streaming_data) {
info.streaming_data.adaptive_formats = iosInfo.streaming_data.adaptive_formats
info.streaming_data.hls_manifest_url = iosInfo.streaming_data.hls_manifest_url

// Use the legacy formats from the original web response as the iOS client doesn't have any legacy formats

for (const format of info.streaming_data.adaptive_formats) {
format.freeTubeUrl = format.url
}

// don't overwrite for live streams
if (!info.streaming_data.hls_manifest_url) {
info.streaming_data.hls_manifest_url = iosInfo.streaming_data.hls_manifest_url
}
}

if (info.streaming_data) {
Expand Down
10 changes: 6 additions & 4 deletions src/renderer/views/Watch/Watch.js
Original file line number Diff line number Diff line change
Expand Up @@ -1176,8 +1176,10 @@ export default defineComponent({
}

if (this.manifestSrc === null ||
// HLS consists of combined audio and video files, so we can't do audio only
((this.isLive || this.isPostLiveDvr) && this.manifestMimeType !== MANIFEST_TYPE_DASH)) {
((this.isLive || this.isPostLiveDvr) &&
// The WEB HLS manifests only contain combined audio and video files, so we can't do audio only
// The IOS HLS manifests have audio-only streams
this.manifestMimeType === MANIFEST_TYPE_HLS && !this.manifestSrc.includes('/demuxed/1'))) {
showToast(this.$t('Change Format.Audio formats are not available for this video'))
return
}
Expand Down Expand Up @@ -1316,10 +1318,10 @@ export default defineComponent({
// live streams don't have legacy formats, so only switch between dash and audio

if (this.activeFormat === 'dash') {
console.error('Unable to play audio formats. Reverting to DASH formats...')
console.error('Unable to play DASH formats. Reverting to audio formats...')
this.enableAudioFormat()
} else {
console.error('Unable to play DASH formats. Reverting to audio formats...')
console.error('Unable to play audio formats. Reverting to DASH formats...')
this.enableDashFormat()
}
} else {
Expand Down