For multiple days now, I've tried to get livestreams running in a WebView on my FireTV stick. I am completely stuck and would appreciate any help I can get.
The website shown works perfectly in the standard browser on my test device. I've already managed to get past all the DRM problems and am able to hear the encrypted sound. But visually, there is nothing. I was unable to find any relevant resources about this. The guides always stop at the point where the media plays. And I'm probably missing some important keyword when googling because I can't actually find a similar case.
I tried to work around this issue and started running my livestreams in an exoplayer instance that sits behind my WebView. That works perfectly fine. But then I noticed that the video elements in the WebView start playing with visuals after the .prepare() function on the exoplayer instance has been called. So I checked out the newest version of the exoplayer. Sadly, I wasn't able to identify any specific action that the exoplayer performs to enable proper playback in the WebView. Do you have any idea what might be missing?
<RelativeLayout>
<FrameLayout/>
<WebView/>
</RelativeLayout>
class MyWebChromeClient : WebChromeClient() {
override fun onPermissionRequest(request: PermissionRequest?) {
request?.let {
it.grant(it.resources)
}
}
}
class MyWebViewClient() : WebViewClient() {
override fun onReceiveSslError(view: WebView?, handler: SslErrorHandler, error: SslError?) {
handler.proceed()
}
override fun shouldOverrideUrlLoading(view: WebView, url: String?): Boolean {
view.loadUrl(url!!)
return true
}
}
class MainActivity : ComponentActivity() {
private lateinit var myWebView: WebView
private lateinit var exoPlayer: ExoPlayer
private lateinit var belowFrame: FrameLayout
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
belowFrame = findViewById(R.id.exo_below) as FrameLayout
myWebView = findViewById(R.id.webview) as WebView
exoPlayer = ExoPlayer.Builder(this).build()
val belowView = PlayerView(this)
belowView.player = exoPlayer
belowFrame.addView(belowView)
myWebView.setWebViewClient(MyWebViewClient())
myWebView.setWebChromeClient(MyWebChromeClient())
myWebView.settings.javaScriptEnabled = true
myWebView.settings.javaScriptCanOpenWindowsAutomatically = true
myWebView.settings.allowContentAccess = true
myWebView.settings.domStorageEnabled = true
myWebView.settings.mediaPlaybackRequiresUserGesture = false
myWebView.settings.allowFileAccess = true
myWebView.setLayerType(View.LAYER_TYPE_HARDWARE, null)
myWebView.setInitialScale(100)
myWebView.loadUrl("localhost:3000")
myWebView.requestFocus()
}
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
if (keyCode == KeyEvent.KEYCODE_BACK) {
myWebView.evaluateJavascript("document.dispatchEvent(new KeyboardEvent('keydown', { keyCode: 8 }))", null)
return true
}
}
// this is called via javascript interface
fun onVideoStart(videoUrl: String) {
runBlocking(Dispatchers.Main.immedate)
{
val
mediaSource = buildMediaSource("http://mcdn.daserste.de/daserste/dash/manifest.mpd")
exoPlayer.setMediaSource(mediaSource)
exoPlayer.prepare() // <= this step makes WebView videos play as expected
}
}
fun buildMediaSource(videoUrl: String): MediaSource {
val dataSourceFactory = DefaultHttpDataSource.Factory()
return DashMediaSource.Factory(dataSourceFactory).createMediaSource(MediaItem.fromUri(videoUrl))
}
}
I've added the folling permissions to the manifest: android.permission.INTERNET android.permission.ACCESS_NETWORK_STATE android.permission.RESOURCE_PROTECTED_MEDIA_ID`
minSdk = 25
targetSdk = 34
compileSdk = 34
After a lot more reading up on this topic and looking into lots of logs I figured it out.
As it turns out you need a SurfaceView below your WebView. As far as I can tell, it doesn't really matter where. All VideoPlayers seem to create or inherit such a view themselves. But the native WebView skips this for some reason.
As soon as you add a Surfaceview, the videos in your WebView will be visible.