Skip to content

Instantly share code, notes, and snippets.

@gold24park
Created October 14, 2022 06:24
Show Gist options
  • Save gold24park/55696bb8c49b90d31b9ea656866d1990 to your computer and use it in GitHub Desktop.
Save gold24park/55696bb8c49b90d31b9ea656866d1990 to your computer and use it in GitHub Desktop.
VimeoPlayer for Android app
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import android.util.AttributeSet
import android.util.Log
import android.view.MotionEvent
import android.view.View
import android.view.View.OnTouchListener
import android.webkit.*
import java.util.*
/**
* 비메오 플레이어
* @reference https://github.com/vimeo/player.js
*/
class WebVideoView : WebView {
var thumbnailUrl: String? = null
var videoUrl: String? = null
private var onPlayVideo: (() -> Unit)? = {}
private var onClickBlock: (() -> Unit)? = {}
var isPlaying = false
constructor(context: Context) : super(context) {
init()
}
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
init()
}
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
init()
}
fun playVideo(
onPlayVideo: () -> Unit = {},
onClick: () -> Unit = {}
) {
this.onPlayVideo = onPlayVideo
this.onClickBlock = onClick
loadVideo()
}
fun clear() {
isPlaying = false
this.videoUrl = ""
this.onPlayVideo = null
this.onClickBlock = null
loadData("", "text/html", "UTF-8")
}
private fun init() {
settings.apply {
javaScriptEnabled = true
domStorageEnabled = true
databaseEnabled = true
allowFileAccess = true
mediaPlaybackRequiresUserGesture = false
cacheMode = WebSettings.LOAD_CACHE_ELSE_NETWORK
}
webChromeClient = object : WebChromeClient() {
override fun getDefaultVideoPoster(): Bitmap? {
val bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
canvas.drawARGB(0, 0, 0, 0)
return bitmap
}
override fun onConsoleMessage(consoleMessage: ConsoleMessage?): Boolean {
if (consoleMessage?.messageLevel() == ConsoleMessage.MessageLevel.ERROR && Random().nextInt(10) == 0) {
}
return super.onConsoleMessage(consoleMessage)
}
}
setOnTouchListener(object : OnClickWithOnTouchListener(context, object : OnClickListener {
override fun onClick() {
onClickBlock?.invoke()
}
}) {
})
addJavascriptInterface(WebVideoInterface(), "Android")
}
private fun loadVideo() {
val html = replace(
HTML, thumbnailUrl ?: "", videoUrl ?: ""
)
loadUrl("about:blank") // LoadData는 한번 Data로딩하면 다시 Refresh 안되어서 trick
loadData(html, "text/html", "UTF-8")
}
inner class WebVideoInterface {
@JavascriptInterface
fun onPlayVideo() {
isPlaying = true
this@WebVideoView.onPlayVideo?.invoke()
}
@JavascriptInterface
fun onLoadingVideo() {
isPlaying = false
this@WebVideoView.onPlayVideo?.invoke()
}
@JavascriptInterface
fun onError(message: String) {
if (message.contains("AbortError", true)) {
// 다시 재생
Log.e("WebVideoView", "Vimeo AbortError, but reloaded")
loadVideo()
} else if (message.isNotEmpty()) {
isPlaying = false
Log.e("WebVideoView", "Vimeo Error: $message")
this@WebVideoView.onPlayVideo?.invoke()
}
}
}
companion object {
private const val HTML = """
<!DOCTYPE html>
<body style="margin: 0; height: 100vh; background-image: url('{0}'); background-repeat : no-repeat; background-size : cover;">
<div id="video_wrapper" style="padding:100% 0 0 0;position:relative;">
<iframe src="{1}&amp;badge=0&amp;autopause=0&amp;background=1&amp;muted=1" frameborder="0" allow="autoplay; fullscreen;" style="position:absolute;top:0;left:0;width:100%;height:100%;" title="square_blocktopia_vid">
</iframe>
</div>
<script src="https://player.vimeo.com/api/player.js"></script>
<script type="text/javascript">
var iframe = document.querySelector('iframe');
var player = new Vimeo.Player(iframe);
Android.onLoadingVideo();
var handleError = function (data) {
if (data != null && Object.keys(data).length > 0) {
Android.onError(JSON.stringify(data));
}
}
player.on('progress', function(data) {
if (data.percent >= 1) {
player.play().then(function() {
console.log('play');
Android.onPlayVideo();
var video = document.querySelector('video');
video.setAttribute('poster', null);
}).catch(handleError)
}
});
</script>
</body>
"""
}
fun replace(sentence: String, vararg values: String): String {
var result = sentence
for ((index, value) in values.withIndex()) {
result = result.replace("{$index}", value)
}
return result
}
}
open class OnClickWithOnTouchListener(
private val mContext: Context,
private val mClickListener: OnClickWithOnTouchListener.OnClickListener?
) :
OnTouchListener {
interface OnClickListener {
fun onClick()
}
private var pressStartTime: Long = 0
private var pressedX = 0f
private var pressedY = 0f
private var stayedWithinClickDistance = false
override fun onTouch(v: View, event: MotionEvent): Boolean {
if (mClickListener == null) {
return false
}
val eventAction = event.action
if (eventAction == MotionEvent.ACTION_DOWN) {
pressStartTime = System.currentTimeMillis()
pressedX = event.x
pressedY = event.y
stayedWithinClickDistance = true
} else if (eventAction == MotionEvent.ACTION_MOVE) {
val distance = distance(pressedX, pressedY, event.x, event.y)
if (stayedWithinClickDistance && distance > OnClickWithOnTouchListener.Companion.MAX_CLICK_DISTANCE) {
stayedWithinClickDistance = false
}
} else if (eventAction == MotionEvent.ACTION_UP) {
val pressDuration = System.currentTimeMillis() - pressStartTime
if (pressDuration < OnClickWithOnTouchListener.Companion.MAX_CLICK_DURATION && stayedWithinClickDistance) {
mClickListener.onClick()
}
}
return false
}
private fun distance(x1: Float, y1: Float, x2: Float, y2: Float): Float {
val dx = x1 - x2
val dy = y1 - y2
val distanceInPx = Math.sqrt((dx * dx + dy * dy).toDouble()).toFloat()
return OnClickWithOnTouchListener.Companion.pxToDp(mContext, distanceInPx)
}
companion object {
/**
* Max allowed duration for a "click", in milliseconds.
*/
const val MAX_CLICK_DURATION = 1000
/**
* Max allowed distance to move during a "click", in DP.
*/
const val MAX_CLICK_DISTANCE = 15
fun pxToDp(context: Context?, px: Float): Float {
return if (context == null) {
0f
} else px / context.resources.displayMetrics.density
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment