iOS UIKit Integration
Pre-Requirements
Download and install XCODE (opens in a new tab). Only available in MacOS systems.
The source code of the next step-by-step guide can be downloaded here (opens in a new tab)
Steps
- Create new project using UIKit
- Create HTMLTemplate file.
- Create WebViewCoordinator class.
- Create VideoPlayerConfiguration structure.
- Create VideoWrapper.
- Modify ViewController.
- Create Custom Betslip.
- Handle player close
Steps detailed
1. Create new project using UIKit
Open XCode and create a new project.
File > New > Project or Shift + Command + N
Make sure that the interface selected is Storyboard.
2. Create HTMLTemplate file.
Create a file named HTMLTemplate.swift
Feel free to create this file wherever you find it convenient.
Create getHTMLString function
This function will interpolate dynamic values based on the configuration object. After this, it will return a string with the HTML that you want to render inside the WebView. You can take a look at our template definition here (opens in a new tab), and in order to get better understanding of this template feel free to read the breakdown section here.
3. WebViewCoordinator class.
Create WebViewCoordinator class file. Feel free to create this file wherever you find it convenient.
This class is responsible for managing the WebView instance and disabling the AVPlayer. We've done this because we want to use the player's custom control bar and functionality.
These are the functionalities that this class provides:
- Ensuring the proper functioning of the video player when transitioning between non-fullscreen and fullscreen modes.
- Handling the communication between the HTML content and the app. This communication is done with the userContentController function.
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
// Handle the message received from JavaScript
if let data = message.body as? [String : Any],
let type = data["type"] {
messageHandler?("\\(type)", nil)
if let payload = data["payload"] {
messageHandler?("\\(type)", payload)
}
}
}
Your file should look like this:
import WebKit
class WebViewCoordinator: NSObject, ObservableObject, WKScriptMessageHandler, WKNavigationDelegate {
@Published var webView: WKWebView
var messageHandler: ((String, Any?) -> Void)?
override init() {
let configuration = WKWebViewConfiguration()
configuration.allowsInlineMediaPlayback = true
configuration.allowsPictureInPictureMediaPlayback = false
let userContentController = WKUserContentController()
configuration.userContentController = userContentController
webView = WKWebView(frame: .zero, configuration: configuration)
super.init()
userContentController.add(self, name: "gsVideoPlayerBridge")
webView.navigationDelegate = self
}
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
// Handle the message received from JavaScript
if let data = message.body as? [String : Any],
let type = data["type"] {
messageHandler?("\(type)", nil)
if let payload = data["payload"] {
messageHandler?("\(type)", payload)
}
}
}
}
4. Create VideoPlayerConfiguration structure.
Create VideoPlayerConfiguration structure file. Place this file wherever you find it convenient.
This structure contains all parameters needed in the HTMLTemplate interpolation.
struct VideoPlayerConfiguration {
var customerId: String = ""
var fixtureId: String = ""
var playerWidth: String = "100vw"
var playerHeight: String = "100vh"
var controlsEnabled: String = "true"
var audioEnabled: String = "true"
var allowFullScreen: String = "true"
var bufferLength: String = "2"
var autoplayEnabled: String = "true"
}
5. Create Video Wrapper.
Create the VideoWrapper file.
This file is in charge of using the WebViewCoordinator and injecting the HTML into the webview.
a. Instantiate the WebViewCoordinator:
class VideoWrapper: UIView {
var webViewCoordinator: WebViewCoordinator!
...
private func setupUI() {
webViewCoordinator = WebViewCoordinator()
...
}
...
}
b. Instantiate the WebViewCoordinator:
private func setupUI() {
...
addSubview(webViewCoordinator.webView)
...
}
c. Interpolate HTMLTemplate with configuration object:
Call the getHTMLTemplate from the HTMLTemplate.swift function with an instance of the VideoPlayerConfiguration.
let configuration = VideoPlayerConfiguration()
let htmlString = getHTMLString(configuration: configuration)
d. Load processed HTML in the Webview:
You need a baseUrl, which can be any URL that is used to load the WebView. In our case, we use the one seen on the code https://www.example.com
let baseURL = "https://www.example.com"
webViewCoordinator.webView.loadHTMLString(
htmlString,
baseURL: URL(string: String(format: baseURL))
)
Your code should look like this:
import UIKit
class VideoWrapper: UIView {
var customBetslip = CustomBetslip()
var webViewCoordinator: WebViewCoordinator!
var messageHandler: ((String, Any?) -> Void)?
init(frame: CGRect, messageHandler: ((String, Any?) -> Void)?) {
self.messageHandler = messageHandler
super.init(frame: frame)
setupUI()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupUI()
}
private func setupUI() {
webViewCoordinator = WebViewCoordinator()
webViewCoordinator.messageHandler = messageHandler
webViewCoordinator.webView.scrollView.bounces = false
webViewCoordinator.webView.scrollView.isScrollEnabled = false
webViewCoordinator.webView.backgroundColor = .black
backgroundColor = .black
addSubview(webViewCoordinator.webView)
addSubview(customBetslip) // Use only if you will implement a custom betslip
updateVideoURL()
}
func updateVideoURL(){
let configuration = VideoPlayerConfiguration()
let baseURL = "https://www.example.com"
let htmlString = getHTMLString(configuration: configuration)
webViewCoordinator.webView.loadHTMLString(
htmlString,
baseURL: URL(string: String(format: baseURL)))
}
// Used for Custom betslip implementations
@objc func updateText(data: [String: Any]) {
var newText = ""
if let newSportsbookFixtureId = data["sportsbookFixtureId"] as? String {
newText += "sportsbookFixtureId: \\(newSportsbookFixtureId) \\n"
}
if let newSportsbookSelectionId = data["sportsbookSelectionId"] as? String {
newText += "sportsbookSelectionId: \\(newSportsbookSelectionId) \\n"
}
if let newSportsbookMarketId = data["sportsbookMarketId"] as? String {
newText += "sportsbookMarketId: \\(newSportsbookMarketId) \\n"
}
if let newSportsbookMarketContext = data["sportsbookMarketContext"] as? String {
newText += "sportsbookMarketContext: \\(newSportsbookMarketContext) \\n"
}
if let newSportsbookMarketId = data["marketId"] as? String {
newText += "marketId: \\(newSportsbookMarketId) \\n"
}
if let newDecimalPrice = data["decimalPrice"] as? Double {
newText += "decimalPrice: \\(newDecimalPrice) \\n"
}
if let newStake = data["stake"] as? Double {
newText += "stake: \\(newStake) \\n"
}
customBetslip.textView.text = newText
customBetslip.isHidden = false
}
override func layoutSubviews() {
super.layoutSubviews()
webViewCoordinator.webView.frame = CGRect(x: 0, y: 0, width: frame.width, height: frame.height)
customBetslip.frame = CGRect(x: 40, y: 40, width: 250, height: 200)
}
}
6. Create ViewController.
Create the ViewController file.
This file is in charge of handling fullscreen and device rotation controls.
a. Instantiate the VideoWrapper:
class ViewController: UIViewController {
private var wrapperView: VideoWrapper!
...
override func viewDidLoad() {
super.viewDidLoad()
wrapperView = VideoWrapper(
frame: CGRect(x: 0, y: 100, width: UIScreen.main.bounds.width, height: 300),
messageHandler: messageHandler
)
...
}
...
}
b. Add the VideoWrapper to the main view:
override func viewDidLoad() {
...
view.addSubview(wrapperView)
...
}
c. Create Subscription and EventHandlers:
- Device Rotation:
You should create a function that tells the app to change orientation. This will be used when changing between fullscreen modes and when loading the views.
func changeOrientation(to orientation: UIInterfaceOrientationMask) {
/** tell the app to change the orientation */
let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene
windowScene?.requestGeometryUpdate(.iOS(interfaceOrientations: orientation))
windowScene?.keyWindow?.rootViewController?.setNeedsUpdateOfSupportedInterfaceOrientations()
}
-
Fullscreen state:
You should implement a function that Toggles the HTML video element's fullscreen as true, this should happen when the app is portrait and is presented.
@objc func toggleFullscreen() { if !isFullscreen { goFullscreen() } else { exitFullscreen() } } func updateViewForRotation(to size: CGSize) { if isVideoPlayerReady { let isLandscape = size.width > size.height if isLandscape { goFullscreen() } else { exitFullscreen() } } } func goFullscreen() { isFullscreen = true wrapperView.webViewCoordinator.webView.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.height) view.layoutIfNeeded() navigationController?.setNavigationBarHidden(true, animated: true) setNeedsStatusBarAppearanceUpdate() let script = """ document.querySelector(".video-container")?.classList.add("full-screen") """ wrapperView.webViewCoordinator.webView.evaluateJavaScript(script) } func exitFullscreen() { isFullscreen = false wrapperView.webViewCoordinator.webView.frame = CGRect(x: 0, y: 100, width: UIScreen.main.bounds.width, height: 300) view.layoutIfNeeded() navigationController?.setNavigationBarHidden(false, animated: true) setNeedsStatusBarAppearanceUpdate() let script = """ document.querySelector(".video-container")?.classList.remove("full-screen") """ wrapperView.webViewCoordinator.webView.evaluateJavaScript(script) }
-
Message handler:
You should implement a function that handles incoming messages from the WebViewCoordinator.
func messageHandler(type: String) { if (type == "toggleFullscreen") { changeOrientation(to: isFullscreen ? .portrait : .landscape) toggleFullscreen() } else if (type == "init") { isVideoPlayerReady = true updateViewForRotation(to: UIScreen.main.bounds.size) } else if (type == "multibet-event") { if let data = payload as? [String: Any] { wrapperView.updateText(data: data) } } } ...
-
Create Subscription:
Subscribe the event handlers to rotation and WebViewCoordinator events.
override func viewDidLoad() { ... wrapperView = VideoWrapper( frame: CGRect(x: 0, y: 100, width: UIScreen.main.bounds.width, height: 300), messageHandler: messageHandler ) ... } ... override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { super.viewWillTransition(to: size, with: coordinator) coordinator.animate(alongsideTransition: { _ in self.updateViewForRotation(to: size) }, completion: nil) } ...
d. Prevent device from locking when user isn't interacting:
In the video view it is recommended to avoid the automatic screen lock, so that the user enjoys a fluid experience without having to
constantly touch the screen. In order to enable this option you must add the following line of code: UIApplication.shared.isIdleTimerDisabled = true
import UIKit
import WebKit
class ViewController: UIViewController {
var isVideoPlayerReady: Bool = false
var isFullscreen: Bool = false
private var wrapperView: VideoWrapper!
override func viewDidLoad() {
super.viewDidLoad()
UIApplication.shared.isIdleTimerDisabled = true
.
.
.
}
When the view is closed, the feature should be disabled with the following code:
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
UIApplication.shared.isIdleTimerDisabled = false
}
e. Fullscreen Landscape Support with Notch Awareness [Optional]:
This step is optional. but if you want to respect the safe area which can reduce the video size, you have to follow this code version: View ViewController.swift on GitHub (opens in a new tab)
We want to offer the best fullscreen experience of the embedded video player when rotating the device to landscape mode. To ensure that the content should never hidden behind the device's notch (also known as the sensor housing or camera cutout) and that all interactive UI remains fully visible.
Key points:
-
Dynamic Notch Detection: The app now detects which side the notch is on (left or right) when the device is rotated into landscape.
-
Safe Area Handling: The video player automatically respects the safe area insets to prevent UI elements from being obstructed.
-
Consistent Fullscreen Behavior: Even when the orientation is changed programmatically or manually by the user, the layout adjusts in real time.
Device Compatibility:
-
iPhones with notch (X and newer): notch padding applied dynamically
-
iPhones without notch: no layout changes
-
iPads: unaffected, layout remains consistent
Why This Matters:
Without proper notch handling, key content (like buttons, stats, or the video stream) may be partially hidden or clipped on devices with a notch. This functionality ensures:
-
Improved usability
-
Better UX consistency
-
Correct video aspect ratio
Adding notch detection:
Let's create to types one for orientation source and other one to identify the position of the notch:
enum NotchPosition: String {
case left = "left"
case right = "right"
case none = "No detected notch"
}
enum OrientationSource: String {
case programatically = "programatically"
case accelerometer = "accelerometer"
case none = "none"
}
We need the following funtion to get the notch detection logic:
func detectNotchSide(safeArea: UIEdgeInsets, orientationSource: OrientationSource, isFullScreen: Bool) -> NotchPosition {
let orientation = UIDevice.current.orientation
switch orientation {
case .landscapeLeft:
return safeArea.left > 0 ? .left : .none
case .landscapeRight:
return safeArea.right > 0 ? .right : .none
case .portrait:
// This detects when the phone is portrait and users go to full screen tapping the fullscreen button on the player control
return isFullScreen && orientationSource == .programatically ? .left : .none
default:
return .none
}
}
Now we are going to add a variable for the orientation source
var orientationSource: OrientationSource = .none
We need to identify when the user taps the fullscreen button. You need to assign the orientation as programatically in the toggleFullscreen function:
@objc func toggleFullscreen() {
if !isFullscreen {
orientationSource = .programatically
goFullscreen()
} else {
...
Now let's add the proper safe area using the notch position in the goFullscreen and changeOrientation functions:
func goFullscreen() {
isFullscreen = true
let insets = view.safeAreaInsets
let screenSize = UIScreen.main.bounds.size
let notchSide = detectNotchSide(safeArea: insets, orientationSource: orientationSource, isFullScreen: isFullscreen)
var x: CGFloat = 0
var width = screenSize.width
switch notchSide {
case .left:
x = insets.left
width -= insets.left
case .right:
width -= insets.right
case .none:
break
}
wrapperView.frame = CGRect(x: x, y: 0, width: width, height: UIScreen.main.bounds.size.height)
wrapperView.webViewCoordinator.webView.frame = wrapperView.bounds
view.layoutIfNeeded()
navigationController?.setNavigationBarHidden(true, animated: true)
setNeedsStatusBarAppearanceUpdate()
let script = """
document.querySelector(".video-container")?.classList.add("full-screen")
"""
wrapperView.webViewCoordinator.webView.evaluateJavaScript(script)
}
func changeOrientation(to orientation: UIInterfaceOrientationMask) {
// tell the app to change the orientation
let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene
windowScene?.requestGeometryUpdate(.iOS(interfaceOrientations: orientation))
windowScene?.keyWindow?.rootViewController?.setNeedsUpdateOfSupportedInterfaceOrientations()
orientationSource = .programatically
}
Finally we need to clean the orientation source when the user goes out from fullscreen:
func exitFullscreen() {
isFullscreen = false
wrapperView.frame = CGRect(x: 0, y: 100, width: UIScreen.main.bounds.width, height: 300)
view.layoutIfNeeded()
navigationController?.setNavigationBarHidden(false, animated: true)
setNeedsStatusBarAppearanceUpdate()
let script = """
document.querySelector(".video-container")?.classList.remove("full-screen")
"""
wrapperView.webViewCoordinator.webView.evaluateJavaScript(script)
orientationSource = .none
}
Your code should look like this:
import UIKit
import WebKit
class ViewController: UIViewController {
var isVideoPlayerReady: Bool = false
var isFullscreen: Bool = false
private var wrapperView: VideoWrapper!
var orientationSource: OrientationSource = .none
override func viewDidLoad() {
super.viewDidLoad()
UIApplication.shared.isIdleTimerDisabled = true
view.backgroundColor = .white
wrapperView = VideoWrapper(
frame: CGRect(x: 0, y: 100, width: UIScreen.main.bounds.width, height: 300),
messageHandler: messageHandler
)
view.addSubview(wrapperView)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
UIApplication.shared.isIdleTimerDisabled = false
wrapperView.onDisappear()
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: { _ in
self.updateViewForRotation(to: size)
}, completion: nil)
}
func messageHandler(type: String, payload: Any) {
if (type == "toggleFullscreen") {
changeOrientation(to: isFullscreen ? .portrait : .landscape)
toggleFullscreen()
} else if (type == "init") {
isVideoPlayerReady = true
updateViewForRotation(to: UIScreen.main.bounds.size)
} else if (type == "multibet-event") {
if let data = payload as? [String: Any] {
wrapperView.updateText(data: data)
}
} else if (type == "betslip-container-dimensions") {
if let data = payload as? [String: Any] {
wrapperView.updateBetslipCoordinates(data: data)
}
}
}
func updateViewForRotation(to size: CGSize) {
if isVideoPlayerReady {
let isLandscape = size.width > size.height
if isLandscape {
goFullscreen()
} else {
exitFullscreen()
}
}
}
@objc func toggleFullscreen() {
if !isFullscreen {
orientationSource = .programatically
goFullscreen()
} else {
exitFullscreen()
}
}
func goFullscreen() {
isFullscreen = true
let insets = view.safeAreaInsets
let screenSize = UIScreen.main.bounds.size
let notchSide = detectNotchSide(safeArea: insets, orientationSource: orientationSource, isFullScreen: isFullscreen)
var x: CGFloat = 0
var width = screenSize.width
switch notchSide {
case .left:
x = insets.left
width -= insets.left
case .right:
width -= insets.right
case .none:
break
}
wrapperView.frame = CGRect(x: x, y: 0, width: width, height: UIScreen.main.bounds.size.height)
wrapperView.webViewCoordinator.webView.frame = wrapperView.bounds
view.layoutIfNeeded()
navigationController?.setNavigationBarHidden(true, animated: true)
setNeedsStatusBarAppearanceUpdate()
let script = """
document.querySelector(".video-container")?.classList.add("full-screen")
"""
wrapperView.webViewCoordinator.webView.evaluateJavaScript(script)
}
func exitFullscreen() {
isFullscreen = false
wrapperView.frame = CGRect(x: 0, y: 100, width: UIScreen.main.bounds.width, height: 300)
view.layoutIfNeeded()
navigationController?.setNavigationBarHidden(false, animated: true)
setNeedsStatusBarAppearanceUpdate()
let script = """
document.querySelector(".video-container")?.classList.remove("full-screen")
"""
wrapperView.webViewCoordinator.webView.evaluateJavaScript(script)
orientationSource = .none
}
func changeOrientation(to orientation: UIInterfaceOrientationMask) {
// tell the app to change the orientation
let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene
windowScene?.requestGeometryUpdate(.iOS(interfaceOrientations: orientation))
windowScene?.keyWindow?.rootViewController?.setNeedsUpdateOfSupportedInterfaceOrientations()
orientationSource = .programatically
}
override var prefersStatusBarHidden: Bool {
return true
}
}
enum NotchPosition {
case left
case right
case none
}
enum OrientationSource: String {
case programatically = "programatically"
case accelerometer = "accelerometer"
case none = "none"
}
func detectNotchSide(safeArea: UIEdgeInsets, orientationSource: OrientationSource, isFullScreen: Bool) -> NotchPosition {
let orientation = UIDevice.current.orientation
switch orientation {
case .landscapeLeft:
return safeArea.left > 0 ? .left : .none
case .landscapeRight:
return safeArea.right > 0 ? .right : .none
case .portrait:
// This detects when the phone is portrait and users go to full screen tapping the fullscreen button on the player control
return isFullScreen && orientationSource == .programatically ? .left : .none
default:
return .none
}
}
7. Create Custom Betslip.
a. Create the CustomBetslip file:
This file is in charge of using the betslip data coming from the video player through the message handler created before and it can be modified as you need.
An example code could look like this:
import UIKit
class CustomBetslip: UIView {
let button1 = UIButton()
let button2 = UIButton()
let textView = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
setupUI()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupUI()
}
private func setupUI() {
isHidden = true
button1.setTitle("Place bet", for: .normal)
button1.addTarget(self, action: #selector(hide), for: .touchUpInside)
button1.backgroundColor = UIColor.blue
button1.layer.cornerRadius = 5
button2.setTitle("Cancel", for: .normal)
button2.addTarget(self, action: #selector(hide), for: .touchUpInside)
button2.backgroundColor = UIColor.blue
button2.layer.cornerRadius = 5
textView.text = "makertId:"
textView.numberOfLines = 10
textView.font = UIFont.systemFont(ofSize: 12)
backgroundColor = UIColor.white.withAlphaComponent(0.8)
layer.cornerRadius = 10
addSubview(button1)
addSubview(button2)
addSubview(textView)
}
@objc func hide() {
isHidden = true
}
override func layoutSubviews() {
super.layoutSubviews()
// Layout your subviews
button1.frame = CGRect(x: 20, y: 140, width: 100, height: 40)
button2.frame = CGRect(x: 140, y: 140, width: 100, height: 40)
textView.frame = CGRect(x: 20, y: 20, width: bounds.width - 40, height: 120)
}
}
b. Instantiate the CustomBetslip in VideoWrapper view:
class VideoWrapper: UIView {
...
var customBetslip = CustomBetslip()
...
}
c. Add the CustomBetslip to VideoWrapper view:
private func setupUI() {
...
addSubview(customBetslip)
...
}
d. Handle events:
Using the messageHandler
function you can listen to market related events.
func messageHandler(type: String, payload: Any) {
if (type == "toggleFullscreen") {
changeOrientation(to: isFullscreen ? .portrait : .landscape)
toggleFullscreen()
} else if (type == "init") {
isVideoPlayerReady = true
updateViewForRotation(to: UIScreen.main.bounds.size)
} else if (type == "multibet-event") {
// Here you will receive all the events related with markets
if let data = payload as? [String: Any] {
wrapperView.updateText(data: data)
}
}
}
Expected payload data structure:
class BetslipData: ObservableObject {
@Published var decimalPrice: Double? = nil
@Published var command: String = ""
@Published var marketId: String = ""
@Published var sportsbookMarketContext: String = ""
@Published var sportsbookMarketId: String = ""
@Published var sportsbookFixtureId: String = ""
@Published var sportsbookSelectionId: String = ""
@Published var stake: String = ""
}
e. Show betslip data:
Here is an example of how you can use the data received in the message
VideoWrapper.swift
class VideoWrapper: UIView {
var customBetslip = CustomBetslip()
...
@objc func updateText(data: [String: Any]) {
var newText = ""
if let newSportsbookFixtureId = data["sportsbookFixtureId"] as? String {
newText += "sportsbookFixtureId: \(newSportsbookFixtureId) \n"
}
if let newSportsbookSelectionId = data["sportsbookSelectionId"] as? String {
newText += "sportsbookSelectionId: \(newSportsbookSelectionId) \n"
}
if let newSportsbookMarketId = data["sportsbookMarketId"] as? String {
newText += "sportsbookMarketId: \(newSportsbookMarketId) \n"
}
if let newSportsbookMarketContext = data["sportsbookMarketContext"] as? String {
newText += "sportsbookMarketContext: \(newSportsbookMarketContext) \n"
}
if let newSportsbookMarketId = data["marketId"] as? String {
newText += "marketId: \(newSportsbookMarketId) \n"
}
if let newDecimalPrice = data["decimalPrice"] as? Double {
newText += "decimalPrice: \(newDecimalPrice) \n"
}
if let newStake = data["stake"] as? Double {
newText += "stake: \(newStake) \n"
}
customBetslip.textView.text = newText
customBetslip.isHidden = false
}
...
}
8. Handle player close.
First we create an onDisappear function on the VideoWrapper, it calls a function in the webViewCoordinator with the same name.
// VideoWrapper.swift
func onDisappear() {
webViewCoordinator.onDisappear()
}
Then, we call this function on the UIViewController inside the viewWillDisappear function so it is called when we expect.
// VideoWrapper.swift
override func viewWillDisappear(_ animated: Bool) {
...
wrapperView.onDisappear()
...
}
Finally, the script in injected into the WebView by writing the onDisappear function in the WebViewCoordinator file.
// WebViewCoordinator.swift
func onDisappear() {
let script = """
if (window.GeniusLivePlayer?.player) {
window.GeniusLivePlayer.player.close()
}
"""
webView.evaluateJavaScript(script)
}