I’m trying to override YouTube default play and pause button by disabling the YouTube video and putting a clear view above it that is tappable. When the clear view is tapped it changes a binding which decides if the video will play or not (this video autoplays also).
Currently the isVideoPlaying variable does change correctly and the HTML does pick up on the change. The problem now is that I added an event listener of "onclick" this is never executed. I need to change this event listener to on Change of isVideoPlaying.
But I don’t know how to do this. The only reason tapping the clear view pauses or plays the video is because when the clear view is tapped swift reloads the whole video which makes it go through the ‘onReady’ section. This is another issue, I don’t want the YouTube video to reload every time the clear view is clicked. I want it to simply play and pause without reloading.
For reference I’m trying to make a Tiktok clone using YouTube shorts.
import SwiftUI
import WebKit
struct SingleVideoView: View {
let link: String
@State private var isVideoPlaying = true
var body: some View {
ZStack {
SmartReelView(link: link, isPlaying: $isVideoPlaying)
Button("", action: {}).disabled(true)//to disable YouTube video
Color.gray.opacity(0.001)
.onTapGesture {
isVideoPlaying.toggle()
}
}
.ignoresSafeArea()
}
}
struct SwiftUIView_Previews: PreviewProvider {
static var previews: some View {
SingleVideoView(link: "-q6-DxWZnlQ")
}
}
struct SmartReelView: UIViewRepresentable {
let link: String
@Binding var isPlaying: Bool
@Environment(.colorScheme) var colorScheme
func makeUIView(context: Context) -> WKWebView {
let webConfiguration = WKWebViewConfiguration()
webConfiguration.allowsInlineMediaPlayback = true
return WKWebView(frame: .zero, configuration: webConfiguration)
}
func updateUIView(_ uiView: WKWebView, context: Context) {
let embedHTML = """
<style>
.iframe-container iframe {
top: 0;
left: 0;
width: 100%;
height: 100%;
}
</style>
<div class="iframe-container">
<div id="player"></div>
</div>
<script>
var tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
var player;
function onYouTubeIframeAPIReady() {
player = new YT.Player('player', {
width: '100%',
videoId: '(link)',
playerVars: { 'autoplay': 1, 'playsinline': 1 },
events: {
'onReady': function(event) {
var isPlaying = ((isPlaying) ? "true" : "false");
if (isPlaying) {
event.target.mute();
if (isPlaying) {
event.target.playVideo();
}
}
}
}
});
}
function watchPlayingState() {
if (isPlaying) {
player.playVideo();
} else {
player.pauseVideo();
}
}
document.addEventListener('click', function() {
watchPlayingState();
});
</script>
"""
uiView.scrollView.isScrollEnabled = false
uiView.loadHTMLString(embedHTML, baseURL: nil)
}
}
Update
Following the supply of an answer below, I have modified the code further:
This new code gets the error "Type ‘SmartReelView’ does not conform to protocol ‘UIViewRepresentable’".
struct SmartReelView: UIViewRepresentable {
let link: String
@Binding var isPlaying: Bool
@Environment(.colorScheme) var colorScheme
var webView: WKWebView?
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, WKNavigationDelegate {
var parent: SmartReelView
init(_ parent: SmartReelView) {
self.parent = parent
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
parent.webView = webView
}
}
func makeUIView(context: Context) -> WKWebView {
let webConfiguration = WKWebViewConfiguration()
webConfiguration.allowsInlineMediaPlayback = true
let webView = WKWebView(frame: .zero, configuration: webConfiguration)
webView.navigationDelegate = context.coordinator
return webView
}
mutating func updateUIView(_ uiView: WKWebView, context: Context) {
if webView == nil {
webView = uiView
let embedHTML = """
<style>
.iframe-container iframe {
top: 0;
left: 0;
width: 100%;
height: 100%;
}
</style>
<div class="iframe-container">
<div id="player"></div>
</div>
<script>
var tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
var player;
var isPlaying = true;
function onYouTubeIframeAPIReady() {
player = new YT.Player('player', {
width: '100%',
videoId: '(link)',
playerVars: { 'autoplay': 1, 'playsinline': 1 },
events: {
'onReady': function(event) {
event.target.mute();
event.target.playVideo();
}
}
});
}
function watchPlayingState() {
if (isPlaying) {
player.playVideo();
} else {
player.pauseVideo();
}
}
</script>
"""
uiView.scrollView.isScrollEnabled = false
uiView.loadHTMLString(embedHTML, baseURL: nil)
}
let jsString = "isPlaying = ((isPlaying) ? "true" : "false"); watchPlayingState();"
webView?.evaluateJavaScript(jsString, completionHandler: nil)
}
}
2
Answers
To achieve an effect where the video auto plays when it appears and plays and pauses (without restarting video) without reloading when the screen is tapped you can use this code (the correct css has been added for a black background as well):
Your Swift code updates
isVideoPlaying
, but your JavaScript code does not detect the change. One standard way to communicate between Swift and JavaScript is usingWKWebView.evaluateJavaScript
. You could use this to run JavaScript code when yourisVideoPlaying
variable changes.And each time you tap the clear view, your video reloads, which is not the behavior you want: you should avoid reloading the entire
WKWebView
. You can control the video’s play and pause state using YouTube’s JavaScript API.So:
SmartReelView
to store a reference toWKWebView
.Coordinator
class to set upWKNavigationDelegate
, as in this thread.evaluateJavaScript
to update theisPlaying
JavaScript variable when the SwiftisVideoPlaying
variable changes.In your JavaScript:
var isPlaying = ...
line from theonReady
function.watchPlayingState()
uses the globalisPlaying
variable.That way, your Swift code directly sets the JavaScript
isPlaying
variable and callswatchPlayingState()
to either play or pause the video accordingly. It does not reload the video, but just uses the YouTube API to control it.