Closed lundjordan closed 10 months ago
Hi @lundjordan,
Thanks for your detailed description.
As the embeddedVideoPlayingNotAllowed
error (Error-Code: 101) is coming directly from the YouTube Player iFrame JavaScript API there is sadly not much which can be easily fixed or debugged why the API returns this error code.
Normally this error code will be returned when the creator of the video disabled the embedding option.
But from looking at the provided code snippet I would recommend to annotate the YouTubePlayer instance variable with an @StateObject
inside your view.
struct YoutubePlayerHelperView: View {
@StateObject
var youTubePlayer: YouTubePlayer
....
}
Additionally, as you app is showing multiple YouTubePlayerViews please keep in mind that simultaneous playback of multiple YouTube players is not supported (Read more)
Thank you for the prompt reply! 🙏🏼
Normally this error code will be returned when the creator of the video disabled the embedding option.
Interesting, in my case I can rule this out as the same video works most of the time and then once in a while, will failed to load with that error
But from looking at the provided code snippet I would recommend to annotate the YouTubePlayer instance variable with an @StateObject inside your view.
Thanks, I will try that.
Additionally, as you app is showing multiple YouTubePlayerViews please keep in mind that simultaneous playback of multiple YouTube players is not supported
Got it. I would actually be interested in not having the youtube videos autoplay but I have a conundrum. I have the videos in a ScrollView and need the user to be able to scroll horizontally across them all. If I don't autoplay
and loopEnabled
, I need to enable the user to have showControls
access. But when I do that, they can no longer scroll with their finger over the video. With built in AVPlayer, a user can swipe anywhere on the video to initiate a scroll, and tap on the play button to play a video. For YouTubePlayerView
I use a hack:
ZStack {
YouTubePlayerView(self.youTubePlayer) // { state in ... etc
// hack to allow for ScrollView scrolling over video
Color.red.opacity(0.0)
}
this allows me to autoplay the video, set showControls
to false, and let the user scroll in ScrollView because they are actually touching a transparent color block overtop of the youtube video.
This might be scope creep of the bug but do you know of a way or support ability to use YoutubePlayer inside a scrollview and be able to scroll when touching inside the video frame? I bet you this would address the error issue as videos would only load/play as needed.
I'll upload an example of how it works right now:
https://github.com/SvenTiigi/YouTubePlayerKit/assets/1648433/f4074258-bc33-4c14-a53e-7ad02ffc5f3d
[...] do you know of a way or support ability to use YoutubePlayer inside a scrollview and be able to scroll when touching inside the video frame?
This is certainly kind a tricky as the underlying view of the YouTubePlayerView
is a WKWebView
and the displayed UI is therefore just HTML, CSS and JS which is makes it hard to implement something like Hit-Testing to check if a touch in a certain point of area should be redirected to next responder.
PS: Thanks for the sponsoring 🙏
I am currently experiencing a simular issue. Not sure if it's the same embeddedVideoPlayingNotAllowed
error, but basically the video will fail to load intermittently. My question is: is there a way to reload the video when this happens?
My ideal use case would be to listen to the statePublisher
and display a refresh button when this returns an error. But I don't really see a way to act on this and to actually reload the YouTube player. Basically looking for a youTubePlayer.reload()
function.
@tyler-fp a dedicated reload
function is currently not available but would be a good addition to the YouTubePlayerKit 👍
For now you can simply re-apply the current configuration which destroys the underlying JavaScript YouTubePlayer instance and performs a re-setup.
extension YouTubePlayer {
func reload() {
self.update(configuration: self.configuration)
}
}
Ah perfect! Simple enough, thanks @SvenTiigi 👍
@tyler-fp FYI the reload
function is now available with the latest release 1.5.3
Hi there,
I started poking this a bit again as some users have complained.
Below is my ugly attempt to reload:
My youtube player view:
struct YoutubePlayerHelperView: View {
@StateObject var youTubePlayer: YouTubePlayer
var partOfPreview: Bool = false
@State private var retryTimes = 0
var body: some View {
ZStack {
YouTubePlayerView(self.youTubePlayer) { state in
// Overlay ViewBuilder closure to place an overlay View
// for the current `YouTubePlayer.State`
switch state {
case .idle:
ProgressView()
case .ready:
EmptyView()
case .error( _):
self.tryReloadingView()
}
}
// hack to allow for ScrollView scrolling over video
Color.red.opacity(0.0)
}
}
func tryReloadingView() -> some View {
if retryTimes < 3 {
self.youTubePlayer.reload()
self.youTubePlayer.mute()
self.youTubePlayer.play()
}
retryTimes += 1
return EmptyView()
}
static func initializePlayer(urlID: String) -> YouTubePlayer {
return YouTubePlayer(
source: .video(id: urlID),
configuration: .init(
showControls: false,
loopEnabled: true
// useModestBranding: true
)
)
}
static func getYoutubePlayer(urlID: String) -> YouTubePlayer {
let youTubePlayer = initializePlayer(urlID: urlID)
youTubePlayer.mute()
youTubePlayer.play()
return youTubePlayer
}
}
my view init call:
YoutubePlayerHelperView(youTubePlayer: YoutubePlayerHelperView.getYoutubePlayer(urlID: youtubeURLID))
behaviour I get:
if the state never hits case .error, calling YoutubePlayerHelperView(youTubePlayer: YoutubePlayerHelperView.getYoutubePlayer(urlID: youtubeURLID))
will load the video and automatically start it playing it because of getYoutubePlayer()
call.
however, if we do hit the intermittent .error case, tryReloadingView()
does successfully reload the video and removes the error view ( I instead see the youtube thumbnail ). But it does not automatically start playing. I was thinking about adding a DispatchQueue.main.asyncAfter
sleep call inside tryReloadingView
in between the reload() and the mute()/play() calls but this all feels like the wrong approach. I need it to autoplay and loop after reload because of the requirement to be able to embed videos inside a scrollview as described in https://github.com/SvenTiigi/YouTubePlayerKit/issues/74#issuecomment-1739826773
Any helps or tips would be appreciated!
I ended up doing:
func tryReloadingView() -> some View {
retryTimes += 1
if retryTimes <= 3 {
self.youTubePlayer.reload()
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
self.youTubePlayer.mute()
self.youTubePlayer.play()
}
return AnyView(EmptyView())
} else {
return AnyView(Text(verbatim: "YouTube player couldn't be loaded"))
}
}
which is not pretty but it does work at least. I noticed sleeping for 1 second was not enough. Needed to up it to 2 seconds.
Hi @lundjordan,
Based on the code snippets you provided I can give you some tips which might be helpful.
tryReloadingView()
function inside the placeholder overlay view builder closure of the YouTubePlayerView
. This overlay view builder closure is called an indefinite number of times by the SwiftUI rendering engine. The best place to call a function such as tryReloadingView()
is via an onReceive
view modifier which listens to the YouTubePlayer.State
.struct YoutubePlayerHelperView: View {
@StateObject
var youTubePlayer: YouTubePlayer
var body: some View {
YouTubePlayerView(self.youTubePlayer) { state in
// ...
}
.onReceive(self.youTubePlayer.statePublisher) { state in
if case .error(let error) = state {
// TODO: Try to reload if necessary
}
}
}
}
Please keep in mind that function calls as reload()
, mute()
or play()
are asynchronous meaning it can take sometime until the operation finished due to the underlying JavaScript communication . It would be better to just call reload()
and react to the change of the state of the YouTubePlayer via the statePublisher
property to call the mute or play function when the YouTubePlayer is in a ready
state.
As the YouTubePlayer
instance is marked with the @StateObject
property wrapper and you passing the instance from the outside of the view you should be keeping this warning from the developer documentation in mind:
Use caution when doing this. SwiftUI only initializes a state object the first time you call its initializer in a given view. This ensures that the object provides stable storage even as the view’s inputs change. However, it might result in unexpected behavior or unwanted side effects if you explicitly initialize the state object.
The best place to call a function such as tryReloadingView() is via an onReceive view modifier which listens to the YouTubePlayer.State.
Ah, self.youTubePlayer.statePublisher
. That makes sense, I changed it and this works! Thank you 🙏🏼
and thanks for the context with 2. and 3.
Closing this issue due to inactivity. Feel free to re-open the issue at any time.
What happened?
Hi,
First, thank you for this library. It's amazing.
I am intermittently hitting a embeddedVideoPlayingNotAllowed error. Maybe 1 out of 10 times when trying to load a youtube video.
We use mostly in-house videos but sometimes use youtube videos for our fitness app. They are in a horizontal scrollview.
Code snippets below
player view:
called here:
from main view:
here's a screenshot of it in use (when working). you can see the youtube video in bottom left corner.
thanks in advance!
What are the steps to reproduce?
described above
What is the expected behavior?
described above