Skip to content

Commit

Permalink
Local API: Use IOS HLS manifest for livestreams (#5705)
Browse files Browse the repository at this point in the history
  • Loading branch information
absidue authored Sep 16, 2024
1 parent 9101171 commit 18f8741
Show file tree
Hide file tree
Showing 4 changed files with 39 additions and 15 deletions.
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 @@ -1186,8 +1186,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 @@ -1326,10 +1328,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

0 comments on commit 18f8741

Please sign in to comment.