How to implement YouTube IFrame Player API in Android using kotlin and jetpack compose?

3.1k views Asked by At

YouTube Android Player API SDK has been fully deprecated. It is no longer supported or maintained by YouTube. So need to implement YouTube IFrame Player API in Android.

When play an YouTube video "An error occurred while initializing YouTube player" message shows.

2

There are 2 answers

1
Sreenath K On

As per developer.google documentation https://developers.google.com/youtube/iframe_api_reference I create a sample app using IFrame YouTube player for android using Kotlin and jetpack compose.

use the below dependency

implementation "androidx.webkit:webkit:1.4.0"

Give internet permission in Manifest.xml

uses-permission android:name="android.permission.INTERNET"

Create a Compose function

@Composable
fun YoutubeVideoPlayer(videoId: String) {
    val webView = WebView(LocalContext.current).apply {
        settings.javaScriptEnabled = true
        settings.loadWithOverviewMode = true
        settings.useWideViewPort = true
        webViewClient = WebViewClient()
    }

    val htmlData = getHTMLData(videoId)

    Column(Modifier.fillMaxSize()) {

        AndroidView(factory = { webView }) { view ->
            view.loadDataWithBaseURL(
                "https://www.youtube.com",
                htmlData,
                "text/html",
                "UTF-8",
                null
            )
        }
        Button(onClick = {
            webView.loadUrl("javascript:playVideo();")
        }) {
            Text(text = "Play Video")
        }
        Button(onClick = {
            webView.loadUrl("javascript:pauseVideo();")
        }) {
            Text(text = "Pause Video")
        }
        Button(onClick = {
            webView.loadUrl("javascript:seekTo(60);")
        }) {
            Text(text = "Seek Video")
        }
    }
}

Use getHTMLData function to load the HTML page contain IFrame

fun getHTMLData(videoId: String): String {
   return """
        <html>
        
            <body style="margin:0px;padding:0px;">
               <div id="player"></div>
                <script>
                    var player;
                    function onYouTubeIframeAPIReady() {
                        player = new YT.Player('player', {
                            height: '450',
                            width: '650',
                            videoId: '$videoId',
                            playerVars: {
                                'playsinline': 1
                            },
                            events: {
                                'onReady': onPlayerReady
                            }
                        });
                    }
                    function onPlayerReady(event) {
                     player.playVideo();
                        // Player is ready
                    }
                    function seekTo(time) {
                        player.seekTo(time, true);
                    }
                      function playVideo() {
                        player.playVideo();
                    }
                    function pauseVideo() {
                        player.pauseVideo();
                    }
                </script>
                <script src="https://www.youtube.com/iframe_api"></script>
            </body>
        </html>
    """.trimIndent()
}

Call compose function in Main activity

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Column {
                YoutubeVideoPlayer(videoId = "bHQqvYy5KYo")
            }
        }
    }
}
1
Happy Dev On

If one wants to implement a native player instead of using the Webview for iFrame API then consider using this wrapper (PierfrancescoSoffritti/android-youtube-player) which internally uses the recommended iFrame-api.

First, add the dependency in your app level gradle, with the latest version:

//youtube Player - iFrame Player
implementation 'com.pierfrancescosoffritti.androidyoutubeplayer:core:{latest-version}'

Then, creating an instance of the class with the required LocalContext, attach it to the AndroidView. This doesn't need any Webview client. This will be a native composable with all the necessary callbacks invoked. Here's how you can do it,

@Composable
fun YoutubeVideoPlayer(
  modifier: Modifier = Modifier,
  youtubeURL: String?,
  isPlaying: (Boolean) -> Unit = {},
  isLoading: (Boolean) -> Unit = {},
  onVideoEnded: () -> Unit = {}
){
  val mContext = LocalContext.current
  val mLifeCycleOwner = LocalLifecycleOwner.current
  val videoId = splitLinkForVideoId(youtubeURL)
  var player : com.pierfrancescosoffritti.androidyoutubeplayer.core.player.YouTubePlayer ?= null
  val playerFragment = YouTubePlayerView(mContext)
  val playerStateListener = object : AbstractYouTubePlayerListener() {
    override fun onReady(youTubePlayer: com.pierfrancescosoffritti.androidyoutubeplayer.core.player.YouTubePlayer) {
      super.onReady(youTubePlayer)
      player = youTubePlayer
      youTubePlayer.loadVideo(videoId, 0f)
    }

    override fun onStateChange(
      youTubePlayer: com.pierfrancescosoffritti.androidyoutubeplayer.core.player.YouTubePlayer,
      state: PlayerConstants.PlayerState
    ) {
      super.onStateChange(youTubePlayer, state)
      when(state){
        PlayerConstants.PlayerState.BUFFERING -> {
          isLoading.invoke(true)
          isPlaying.invoke(false)
        }
        PlayerConstants.PlayerState.PLAYING -> {
          isLoading.invoke(false)
          isPlaying.invoke(true)
        }
        PlayerConstants.PlayerState.ENDED -> {
          isPlaying.invoke(false)
          isLoading.invoke(false)
          onVideoEnded.invoke()
        }
        else -> {}
      }
    }

    override fun onError(
      youTubePlayer: com.pierfrancescosoffritti.androidyoutubeplayer.core.player.YouTubePlayer,
      error: PlayerConstants.PlayerError
    ) {
      super.onError(youTubePlayer, error)
      Console.debug("iFramePlayer Error Reason = $error")
    }
  }
  val playerBuilder = IFramePlayerOptions.Builder().apply {
    controls(1)
    fullscreen(0)
    autoplay(0)
    rel(1)
  }
  AndroidView(
    modifier = modifier.background(Color.DarkGray),
    factory = {
      playerFragment.apply {
        enableAutomaticInitialization = false
        initialize(playerStateListener, playerBuilder.build())
      }
    }
  )
  DisposableEffect(key1 = Unit, effect = {
    mContext.findActivity() ?: return@DisposableEffect onDispose {}
    onDispose {
      playerFragment.removeYouTubePlayerListener(playerStateListener)
      playerFragment.release()
      player = null
    }
  })
  DisposableEffect(mLifeCycleOwner) {
    val lifecycle = mLifeCycleOwner.lifecycle
    val observer = LifecycleEventObserver { _, event ->
      when (event) {
        Lifecycle.Event.ON_RESUME -> {
          player?.play()
        }
        Lifecycle.Event.ON_PAUSE -> {
          player?.pause()
        }
        else -> {
          //
        }
      }
    }
    lifecycle.addObserver(observer)
    onDispose {
      lifecycle.removeObserver(observer)
    }
  }
} 

private fun splitLinkForVideoId(
  url: String?
): String {
  return (url!!.split("="))[1]
}

You can make customisations in the controls/fullscreen/autoPlay etc. of the player by making changes in the IFramePlayerOptions.Builder()

Credits to Pierfrancesco Soffritti :)

IMP! Didn't get any workaround for disabling Ads.