Android Question Play YouTube Video on WebView or SMM (SOLVED)

walterf25

Expert
Licensed User
Longtime User
Hello everyone, i've been searching for a couple of days and have not found a reliable way to play a youtube video the way I need to.

I have a video that I will be showing in my app, it's a tutorial video, I need the video to automatically start playing as soon as it loads and it needs to play with the sound on.

I have tried this using a WebView, and although it loads the video, it seems i need some js knowledge to be able to play the video and unmute it when it starts playing. I have been able to generate some js scripts using chatgpt and other sources on the web, but none seem to do what I need to.

I also tried using Erel's Simple Media Manager library, which as I understand works with ExoPlayer, and the way it works is by loading the video in a webview since it seems that the video can not be played or loaded natively with ExoPlayer.

With all this said, I need to find a solution, Here's what I have tried so far:
WebView Player:
    Dim html As String = $"<!DOCTYPE html>
<html>
  <body>
    <div id="player"></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', {
          height: '100%',
          width: '100%',
          videoId: '${videoID}',
          playerVars: {
    'autoplay': 0,
    'mute': 1,  // Start muted to bypass autoplay restriction
    'controls': 1,
    'playsinline': 1
          },
          events: {
    'onReady': onPlayerReady
          }
        });
      }

      function onPlayerReady(event) {
        event.target.playVideo();
        setTimeout(function() {
             player.unMute(); // Unmute after 500 milliseconds
        }, 500);
      }
    </script>
  </body>
</html>
"$

This code loads the video on a webview and it starts playing automatically, but after the 500 milliseconds timer, it unmutes the video but then the Video stops playing, I've read in another forum that you can not start a video unmuted as it is a restriction by YouTube, but I still think there's got to be a way to do it, i'm just not very good at js.

I'd like to read other people's opinions on this, Has anyone else encountered this, if so how did you figure it out?

Thanks,
Walter
 

walterf25

Expert
Licensed User
Longtime User
If Anyone is interested here's the updated js String, I've added the Style tags in the HTML, and looks like this did the trick. Also added some functions to be able to start the video playing from a Sub in B4X.

JavaScript:
    Dim html As String = $"<!DOCTYPE html>
<html style="height: 100%; margin: 0;">
     <body>
    <div id="player" style="height: 100%; width: 100%;"></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', {
          height: '100%',
          width: '100%',
          videoId: '${videoID}',
          playerVars: {
    'autoplay': 0,
    'mute': 0,  // Start muted to bypass autoplay restriction
    'controls': 1,
    'playsinline': 1
          },
          events: {
    'onReady': onPlayerReady
          }
        });
      }

      function onPlayerReady(event) {
            B4X.CallSub("Player_Ready", true);
      }
      
      function playVideo() {
          player.playVideo();
      }
      
       // This function will stop the video when called from B4X
      function stopVideo() {
        player.stopVideo();
      }
      
      function unMuteVideo() {
          player.unMute();
      }
      
      function getPlayerDimensions() {
          var playerElement = document.getElementById('player');
        var height = playerElement.offsetHeight;
        var width = playerElement.offsetWidth;
        callB4aSub('Player_Dimensions', { Dimensions: [height, width] } )
      }
      
      function callB4aSub(b4xSubName, values) {
        setTimeout(function() {
          if (!values) {
            return B4X.CallSub('CallB4xUiSub_Async', true, b4xSubName, "");
          }

              return B4X.CallSub('CallB4xUiSub_Async', true, b4xSubName, JSON.stringify(values));
        }, 0);
  }
    </script>
  </body>
</html>
"$
 
Upvote 0

Gary Miyakawa

Active Member
Licensed User
Longtime User
Code is on post #2
Yes, thank you for that but I was also looking for the usage of that code. I have included that into a very simple test code but I keep getting an error from youtube that it can't play that video...

The actual message from YouTube is: "An error has occurred. Please try again later. "

I'm open to any suggestions.

Thank you !

My code is:

B4X:
#Region Project Attributes
    #MainFormWidth: 600
    #MainFormHeight: 600
#End Region

Sub Process_Globals
    Private fx As JFX
    Private MainForm As Form
    Private xui As XUI
    Private WebView1 As WebView
End Sub

Sub AppStart (Form1 As Form, Args() As String)
    
    Dim videoid As String = "http://www.youtube.com/watch?v=QJe1tGI1EZc"
    
    Dim html As String = $"<!DOCTYPE html>
<html style="height: 100%; margin: 0;">
     <body>
    <div id="player" style="height: 100%; width: 100%;"></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', {
          height: '100%',
          width: '100%',
          videoId: '${videoid}',
          playerVars: {
    'autoplay': 1,
    'mute': 0,  // Start muted to bypass autoplay restriction
    'controls': 1,
    'playsinline': 1
          },
          events: {
    'onReady': onPlayerReady
          }
        });
      }

      function onPlayerReady(event) {
            B4X.CallSub("Player_Ready", true);
      }
      
      function playVideo() {
          player.playVideo();
      }
      
       // This function will stop the video when called from B4X
      function stopVideo() {
        player.stopVideo();
      }
      
      function unMuteVideo() {
          player.unMute();
      }
      
      function getPlayerDimensions() {
          var playerElement = document.getElementById('player');
        var height = playerElement.offsetHeight;
        var width = playerElement.offsetWidth;
        callB4aSub('Player_Dimensions', { Dimensions: [height, width] } )
      }
      
      function callB4aSub(b4xSubName, values) {
        setTimeout(function() {
          if (!values) {
            return B4X.CallSub('CallB4xUiSub_Async', true, b4xSubName, "");
          }

              return B4X.CallSub('CallB4xUiSub_Async', true, b4xSubName, JSON.stringify(values));
        }, 0);
  }
    </script>
  </body>
</html>
"$
    
    MainForm = Form1
    MainForm.RootPane.LoadLayout("Layout1")
    MainForm.Show
    
    
    WebView1.LoadHtml(html)
    
End Sub
 
Upvote 0

walterf25

Expert
Licensed User
Longtime User
Try videoid = "QJe1tGI1EZc" without the entire URL
 
Upvote 0

walterf25

Expert
Licensed User
Longtime User
Upvote 0

walterf25

Expert
Licensed User
Longtime User
Yes, it is a live stream... Is that a problem? Is there a way around the issue?
I did a quick test, and based on my findings You are trying to use my code in B4J, I am using it in B4A and while it works fine for my purpose, I had to do a little digging to get it to work with B4J, here's the modified javascript code to be able to raise events from within JS to B4J:

YouTube video:
Private Sub Button1_Click
    Dim videoid As String = "QJe1tGI1EZc" '''"gOO5XtCvSe8"
    '''"http://www.youtube.com/watch?v=QJe1tGI1EZc"
    
    Dim jo As JavaObject = WebView1
    Dim webEngine As JavaObject = jo.RunMethod("getEngine", Null)

    ' Set an alert handler to process JavaScript calls
    webEngine.RunMethod("setOnAlert", Array(jo.CreateEvent("javafx.event.EventHandler", "Alert", False)))
    
    
    Dim html As String = $"<!DOCTYPE html>
<html style="height: 100%; margin: 0;">
<body>
  <div id="player" style="height: 100%; width: 100%;"></div>

  <script>
    // Load YouTube IFrame API
    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;

    // Called when YouTube IFrame API is ready
    function onYouTubeIframeAPIReady() {
      player = new YT.Player('player', {
        height: '100%',
        width: '100%',
        videoId: '${videoid}', // Video ID dynamically set
        playerVars: {
    'autoplay': 0, // No autoplay to avoid restrictions
    'mute': 0, // Video starts unmuted
    'controls': 1,
    'playsinline': 1
        },
        events: {
    'onReady': onPlayerReady,
    'onError': onPlayerError
        }
      });
    }

    // Called when the player is ready
    function onPlayerReady(event) {
      // Notify B4J that the player is ready
      callB4jSub('Player_Ready', 'true');
    }

    // Called when there is an error in the player
    function onPlayerError(event) {
      console.error("YouTube Player Error:", event.data);
      callB4jSub('Player_Error', event.data.toString());
    }

    // Function to play video
    function playVideo() {
      player.playVideo();
    }

    // Function to stop video
    function stopVideo() {
      player.stopVideo();
    }

    // Function to unmute video
    function unMuteVideo() {
      player.unMute();
    }

    // Function to notify B4J about dimensions
    function getPlayerDimensions() {
      var playerElement = document.getElementById('player');
      var height = playerElement.offsetHeight;
      var width = playerElement.offsetWidth;
      callB4jSub('Player_Dimensions', JSON.stringify({ Dimensions: [height, width] }));
    }

    // Function to call B4J method
    function callB4jSub(methodName, value) {
      try {
        // Use alert for communication with B4J WebView
        alert(methodName + ':' + value);
      } catch (err) {
        console.error("Error in callB4jSub:", err.message);
      }
    }
  </script>
</body>
</html>"$

WebView1.LoadHtml(html)
End Sub

Private Sub Player_Ready(value As Object)
    Log("called from js: " & value)
    Dim Play As String = $"playVideo()"$
    
    Dim engine As JavaObject
    engine = WebView1.As(JavaObject).RunMethod("getEngine", Null)
    '''WebView1.As(JavaObject).RunMethodJO("getEngine", Null).RunMethod("executeScript", Array(Play))
    engine.RunMethod("setJavaScriptEnabled", Array(True))
    Log("javacript is enabled: " & engine.RunMethodjo("javaScriptEnabledProperty", Null).RunMethod("getValue", Null))
    engine.RunMethod("executeScript", Array(Play))
    
End Sub

Private Sub Player_Error(error As String)
    Log("error: " & error)
End Sub

The setOnAlert function Exposes the Bridge to JavaScript, Attaches the bridge to the WebView using JavaObject.

In the case of the live stream video you are trying to play, it will not work because you will receive the following errors:

  • 101 or 150: The video owner has disabled embedding.
  • 5: The requested content cannot be played in the HTML5 player.
Error 150 means the video owner has disabled embedding.
If you try a different videoID you will see that it works as expected.

If you are the owner or know the owner of the live stream video you are trying to play, you might want to ask them to enable embedding so you can play the video.

Hope this helps, I'am attaching a small project example:
 

Attachments

  • YouTubeTest.zip
    4.3 KB · Views: 24
Upvote 0

Gary Miyakawa

Active Member
Licensed User
Longtime User
THANK YOU SO MUCH! I do some playing around with what you built and testing with the various sites I'm trying to get to!

THANK YOU AGAIN !

Happy Holidays !

Gary M
 
Upvote 0

Baublanc

Member
Licensed User
Longtime User
Also requesting sample code for this YouTube viewer in B4A.

I try it but playvideo don't work. It play at the beginning with mute but in remove the mute and the video stop and unable the start the video.

Thanks,
 
Upvote 0

walterf25

Expert
Licensed User
Longtime User
Also requesting sample code for this YouTube viewer in B4A.

I try it but playvideo don't work. It play at the beginning with mute but in remove the mute and the video stop and unable the start the video.

Thanks,
That is the default behavior, YouTube's API does not allow you to play the video automatically unless is muted due to user experience policies and browser autoplay restrictions.
 
Upvote 0

walterf25

Expert
Licensed User
Longtime User
Thanks walterf25
This is my relevant code for B4A

Play Video:
Private Sub btnPlay_Click
    If pnlMedia.Visible = False Then pnlMedia.SetVisibleAnimated(250, True)
    pnlParent.SetVisibleAnimated(100, False)
    Dim p As Phone
    p.SetScreenOrientation(0)
    
    Dim html As String = $"<!DOCTYPE html>
<html style="height: 100%; margin: 0;">
 // <body style="height: 100%; margin: 0;">
     <body>
    <div id="player" style="height: 100%; width: 100%;"></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() {
          try {
        player = new YT.Player('player', {
          height: '100%',
          width: '100%',
          videoId: '${videoID}',
          playerVars: {
    'autoplay': 1,
    'mute': 1,  // Start muted to bypass autoplay restriction
    'controls': 1,
    'playsinline': 1
          },
          events: {
    'onReady': onPlayerReady
          }
        });
      } catch (error) {
          handleError("Error initializing YouTube player", error);
      }
     }

      function onPlayerReady(event) {
            B4X.CallSub("Player_Ready", true);
      }
      
      function playVideo() {
      try {
          player.playVideo();
         B4X.CallSub("Video_Playing", true);
        } catch (error) {
            handleError("Error in onPlayerReady", error);
        }
      }
      
       // This function will stop the video when called from B4X
      function stopVideo() {
        player.stopVideo();
      }
      
      function unMuteVideo() {
      try {
          player.unMute();
        B4X.CallSub("Video_unMuted", true);
        } catch (error) {
        B4X.CallSub("Error_Message", true);
            handleError("Error unmuting video", error);
        }
      }
      
      function getPlayerDimensions() {
          var playerElement = document.getElementById('player');
        var height = playerElement.offsetHeight;
        var width = playerElement.offsetWidth;
        callB4aSub('Player_Dimensions', { Dimensions: [height, width] } )
      }
      
      function callB4aSub(b4xSubName, values) {
        setTimeout(function() {
          if (!values) {
            return B4X.CallSub('CallB4xUiSub_Async', true, b4xSubName, "");
          }

              return B4X.CallSub('CallB4xUiSub_Async', true, b4xSubName, JSON.stringify(values));
        }, 0);
  }
 
 
        // Global error handler
      window.addEventListener("error", function (event) {
        handleError("Global Error", event.error || event.message);
      });

      // Error handler to log and pass errors to B4X
      function handleError(context, error) {
        var errorMessage = context + ": " + (error.message || error);
        console.error(errorMessage);
        callB4aSub('Handle_JavascriptError', { error: [context, error] });
        // Pass error to B4X
        try {
            callB4aSub('Handle_JavascriptError', { error: [context, error.message] });
        } catch (innerError) {
          console.error("Error in handleError: ", innerError);
        }
      }
    </script>
  </body>
</html>
"$
    
    WebView1.JavaScriptEnabled = True
    wbe.addWebChromeClient(WebView1, "webviewclient")
    wbs.setAppCacheEnabled(WebView1, True)
    wbs.setDefaultZoom(WebView1, "FAR")
    wbs.setDOMStorageEnabled(WebView1, True)
    wbs.setLoadWithOverviewMode(WebView1, False)
    wbe.AddJavascriptInterface(WebView1, "B4X")
    '''WebView1.LoadUrl("https://drive.google.com/file/d/1vFJlzi5GdJUNiyv8J1vXndrV9oIeAQdv/preview")
    WebView1.LoadHtml(html)
End Sub

I have added some error handling to capture any issues while trying to play or unmuting the video, you can play around with the code and see if it meets your needs.

Here's the subs that are called from within the JavaScript code as well

B4X:
Private Sub Player_Ready
    Log("called from js")
    Dim Play As String = $"playVideo()"$
    Dim unmute As String = $"unMuteVideo()"$
'''    wbe.executeJavascript(WebView1, unmute)
'''    Sleep(500)
    wbe.executeJavascript(WebView1, Play)
'''    Sleep(250)
'''    Dim js As String = $"stopVideo()"$
'''    wbe.ExecuteJavascript(WebView1, js)
'''    Sleep(1000)
'''    wbe.executeJavascript(WebView1, Play)
'''    Sleep(500)
'''    wbe.executeJavascript(WebView1, unmute)
'''    Sleep(250)
'''    wbe.executeJavascript(WebView1, Play)
End Sub

Private Sub Video_Playing
    LogColor("playVideo() called", xui.Color_Blue)
End Sub

Private Sub Video_unMuted
    LogColor("unMuteVideo() called", xui.Color_Blue)
End Sub

Private Sub Player_Dimensions(dimensions As String)
    Dim data As Map = ReadWebviewJsObjectData(dimensions)
    Dim screendimensions As List = data.Get("Dimensions")
    Log("player width: " & screendimensions.Get(1))
    Log("player height: " & screendimensions.Get(0))
End Sub

Private Sub Handle_JavascriptError(ErrorDetails As Map)
'''    Log("JavaScript Error Context: " & ErrorDetails.Get("context"))
'''    Log("JavaScript Error Message: " & ErrorDetails.Get("message"))
'''    Log("JavaScript Error Stack: " & ErrorDetails.Get("stack"))
    ' Take additional actions as needed, such as showing an alert or retrying
    Log("errordetails: " & ErrorDetails)
End Sub

Public Sub CallB4xUiSub_Async(subName As String, parameterDatas As String)
    'Call a sub in Main Activity or Form
    Log("subName: " & subName)
    If parameterDatas.Length = 0 Then
        CallSubDelayed(Me, subName)
    Else
        CallSubDelayed2(Me, subName, parameterDatas)
    End If
End Sub

Public Sub ReadWebviewJsObjectData(json As String) As Map
    If Not(json.Trim = "") Then
        Private JSONParserEngine As JSONParser
        JSONParserEngine.Initialize(json)
        Return JSONParserEngine.NextObject
    End If
    
    Return CreateMap()
End Sub

Cheers
Walter
 
Upvote 0
Cookies are required to use this site. You must accept them to continue using the site. Learn more…