Created
October 14, 2022 06:24
-
-
Save gold24park/55696bb8c49b90d31b9ea656866d1990 to your computer and use it in GitHub Desktop.
VimeoPlayer for Android app
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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}&badge=0&autopause=0&background=1&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