intermittently hitting YouTubePlayerKit.YouTubePlayer.Error.embeddedVideoPlayingNotAllowed error
lundjordan opened this issue ยท 13 comments
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:
struct YoutubePlayerHelperView: View {
let youTubePlayer: YouTubePlayer
var partOfPreview: Bool = false
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(let error):
Text(verbatim: "YouTube player couldn't be loaded")
}
}
// hack to allow for ScrollView scrolling over video
Color.red.opacity(0.0)
}
}
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
}
}
called here:
struct ExerciseMiniVideoPlayer: View {
...
...
var body: some View {
VStack {
YoutubePlayerHelperView(youTubePlayer: YoutubePlayerHelperView.getYoutubePlayer(urlID: youtubeURLID), partOfPreview: true)
from main view:
GeometryReader { geometry in
HStack {
ScrollViewReader { scrollViewReaderValue in
ScrollView(.horizontal) {
HStack(spacing: 10) {
ForEach(workout.workoutPartsArray { workoutPart in
ExerciseMiniVideoPlayer(workoutPart: workoutPart, exerciseSet: ..., exercise: exerciseID)
.frame(width: (geometry.size.width * 0.9), height: ((geometry.size.width * 9) / 16) * 0.9)
.cornerRadius(10)
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
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:
Simulator.Screen.Recording.-.iPhone.15.Pro.-.2023-09-28.at.11.35.59.mp4
[...] 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 ๐
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 ofgetYoutubePlayer()
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 aDispatchQueue.main.asyncAfter
sleep call insidetryReloadingView
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 #74 (comment)
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.
- Do not invoke any kind of side effect such as calling the
tryReloadingView()
function inside the placeholder overlay view builder closure of theYouTubePlayerView
. 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 astryReloadingView()
is via anonReceive
view modifier which listens to theYouTubePlayer.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()
orplay()
are asynchronous meaning it can take sometime until the operation finished due to the underlying JavaScript communication . It would be better to just callreload()
and react to the change of the state of the YouTubePlayer via thestatePublisher
property to call the mute or play function when the YouTubePlayer is in aready
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.