jessuni / shikwasa

An audio player born for podcast
https://shikwasa.js.org
MIT License
477 stars 31 forks source link

Live stream support? #21

Closed smilingpeanut closed 3 years ago

smilingpeanut commented 4 years ago

First, thank you for this wonderful package! I love this player and I'm planning to use it on a project that allows the user to switch between live streams and on-demand podcasts. The player works with some modifications, namely disabling the seek bar and time display. Currently I'm doing this in Vue.js using a computed state property that sets an .is-live class to the wrapper div and adding this CSS to hide the components:

.is-live .shk-bar_wrap, 
.is-live .shk-display {
  display: none;
}

This works somewhat well but I'd like to see official support. I also have to use the internal updateAudioData method instead of the update method to change the track display because update interrupts playback.

jessuni commented 4 years ago

I'm not familiar with HLS but it seems the current player can play live stream audio well? Is there any support you need other than styles and an uninterrupted audio update?

If Shikwasa's gonna support HLS, it'd be better to support the whole pack.

smilingpeanut commented 4 years ago

The actual audio stream plays great. (Not using HLS, but an AAC audio stream.)

Yep, there are two needs since the audio plays fine.

  1. A way to safely update metadata without stopping playback
  2. Some way of signaling that it's a live stream so that the appropriate controls (seek, rewind/forward) are disabled, and if possible, to disable the parser since it can't parse anything from the stream. (Currently it registers a console error when using a parser.)

Maybe by passing a duration of -1 to indicate a live stream? Or a live parameter?

Thanks so much!

smilingpeanut commented 4 years ago

Somewhat related to this, Chrome (and possibly other browsers, I haven't tested) disables audio from background tabs after hitting some sort of inactivity threshold. My own research on how to override this has not presented any definitive action to be implemented to counteract it, but it's something to keep in mind.

jessuni commented 3 years ago

@smilingpeanut BTW I noticed that you customized the player in your website and the UI looks much better. I'm planning to provide more style customization options for player in v2.1.0, do you mind me using the skin to demonstrate in Shikwasa's demo website?

smilingpeanut commented 3 years ago

do you mind me using the skin to demonstrate in Shikwasa's demo website?

No, I don't mind at all! Thank you! 😃

smilingpeanut commented 3 years ago

@jessuni In testing, this seems to work well. Metadata updates and doesn't affect playback. When the live parameter is specified, it doesn't trigger the parser. The only issue that I see (and this could be specific to our streams, I have no idea), is that when playing a live stream, the loading indicator stays on and never goes away.

jessuni commented 3 years ago

Ahh I forgot to test the loader! I'll look into it.

jessuni commented 3 years ago

@smilingpeanut I can't reproduce the issue with your audio source (https://stream.loudspeaker.fm/radio/8020/talk) locally but I did see the problem on your website. It's not relative to this update though:

When clicking play button, the player fires a waitiing event which will trigger the loader to show. But when clicking pause or the player fires canplaythrough, the loader will disappear. I've tried different streams, including yours, and all will fire the canplaythrough event. Can you check if this is the cause?

smilingpeanut commented 3 years ago

@jessuni I'm not quite sure I understand. When testing commit 3df7c28 on my local environment, the loader appears every time a live stream is played and does not disappear unless you pause and then play again. Is this the intended behavior? The code that triggers the player is simple:

this.$player.update(state.nowPlaying); this.$player.play();

jessuni commented 3 years ago

@smilingpeanut

When clicking play button, the player fires a waiting event which will trigger the loader to show. But when clicking pause or the player fires canplaythrough, the loader will disappear.

This is the internal logic that determines whether the loader should display or not. When waiting is fired, the attribute data-loading will be added to the wrapper. The attribute gets removed when canplaythrough is fired or when pause is hit. On the CSS side, the loader has display:inline-block when the attribute is there and display: none when not.

I was saying that on your website I can see the problem is already there, it might not be generated from this commit. Can you check in your local environment if canplaythrough is fired? image I also made a fiddle https://jsfiddle.net/jessuni/Lf2k09pt/3/ with your audio stream and the version you're using in production, everything seem to be working just fine.

smilingpeanut commented 3 years ago

@jessuni

Okay, that makes sense. In testing on my local environment, it looks like canplaythrough is not being fired. What needs to happen to trigger this?

jessuni commented 3 years ago

@smilingpeanut here's the spec and MDN reference.

Normally when an audio source finishes preloading and has enough data to play to the 'end', this event will be automatically triggered. It shouldn't require any further actions. Before the fiddle I was presuming the cause might be audio streams not firing canplaythrough because there wouldn't be any 'end', according to the docs?

Well, but now it seems not to be the case. Since it works without framework, could the cause be Vue? Could you check if you initiated the instance somewhere that's blocking the audio?

In a worse case scenario, there are still workarounds like manipulating the loader's appearance on certain events.

jessuni commented 3 years ago

I updated the fiddle using Vue :) https://jsfiddle.net/jessuni/Lf2k09pt/12/ Hope this could help you debug.

smilingpeanut commented 3 years ago

Thanks so much for the fiddle. I did some troubleshooting and the issue is definitely not with Shikwasa, but either the stream itself not sending the proper audio event or my implementation of the player in Nuxt that's short-circuiting somewhere. In either case, I think we're all good. For now, I'm using this workaround:

this.$player.on('playing',` () => {
  if (this.$player._audio.live) {
    this.$player.el.removeAttribute('data-loading')
  }
})

The only other thing I've noticed in testing (the build is live on loudspeaker.fm now) is that the live indicator on mobile is in a weird place. Screenshot: https://cloudup.com/cTZKjsOy1BS

Perhaps it might look better with something like this mockup: https://cloudup.com/cHuZmAL9HoL.

Thanks again for all your help!

jessuni commented 3 years ago

I just fixed the style bug before seeing your post! ;-) I'd love to make the player minimal, leaving out the mute button, the download button, the chapter button etc., removing the CSS-heavy hamburger icon, and it definitely looks much cooler that way (I really do!). Your sense of design definitely brings the player to the next level.

For now the hamburger (or three dots?) button can't be removed, because there are always feature requests for new controls such as volume controls (#23). Meanwhile, I'm struggling not to squeeze the buttons into a <20px space just to make way for a cover picture like what some mainstream players do, as it makes touching so difficult on mobile screens. So I'm putting both principles in mind when designing. It's hard balancing between being a full-featured and widely applicable player vs. biased, minimal player with its personality. I'm also thinking about making the player collapsable in mobile screens so that it could also fit covers and other stuff while maintaining accessible.

I really appreciate that you actually built a mockup for this tiny element and share your thoughts with me. Despite the fact that as a library author I'm tied to above responsibilities, you're still free to implement it on your website (how lucky is that).

smilingpeanut commented 3 years ago

Thank you so much for your kind words! As a fellow developer, I understand balancing the needs vs. the wants so no issue there!

I just noticed something else in testing. The scrolling animation that happens when the title is too long for the viewport. This updates when you resize the window but not when the source/metadata changes. So if the title you're changing to is longer than the previous metadata, it does not animate and appears cut off. I think this could be fixed by running the same check on updateMetadata as you do on window resize.