# JsView媒体播放的解决方法
因为Android盒子的碎片化,导致很多播放器异常发生。
为了让用户能够根据自己的场景自定义播放器,JsView有三个播放器集成的建议:
# 方案1. apk直接打包播放器
# 1. apk直接集成播放器,
通过MiniApp或者MainProxy的AddJavaScriptInterface接口将play/pause等方法传给js调用。
通过MiniApp获得的JsView句柄,通过其emitEvent接口,将timeupdate的信息发给js。
# 2. js通过组建做一个透过的区域
透过区域的制作方法有两种: 1. JsvHole组件, 2. JsvNativeSharedView组件(会讲其top,left,width,height信息同步给Java)
# 方案2. 撰写播放器插件
相对于将播放器集成到apk,插件的方式方便后续对播放器接口进行更新。但缺点也是,插件机制不支持resource导入,只能纯java逻辑。
# 1. 撰写插件处理
# 2. js加载插件,并做一个透过的区域
透过区域的制作方法有两种: 1. JsvHole组件, 2. JsvNativeSharedView组件(会讲其top,left,width,height信息同步给Java)
# 方案3. 直接使用JsView关联的商用版的播放器(需要授权)
此为vue的组件,组件名为 JsvPlayer,使用方法和 video 标签一致,这是一个平台适配好的播放器插件,适配android 4.4以上的设备,提供包括rtsp等IPTV直播播放协议,并支持播放变速,PIP等播放行为。此为增值服务,需要时请直接联系我们。
# 附录1. JsvNativeSharedView组件, js java对接方法
# 1. js端
范例代码如下,流程为,创建JsvNativeSharedView,并赋予宽高和visibility,并注册一个回调函数接受view的id值。 当收到view的id后,将此id同步通知给java端,java需要此id来进行对view的位置变化事件进行监听。
<script setup>
const getId = (viewId)=>{
// TODO: 将viewId通过java穿透的接口通知给java端, 例如: 当java穿透对象为 jNativeMedia 接口为 syncViewId 时
window.jNativeMedia?.syncViewId(viewId);
}
</script>
<template>
<jsv-native-shared-div :getId="getId" :style="{
left: 0,
top: 0,
width: 500,
height: 500,
visibility: 'visible'
}"/>
</template>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 2. java端
范例代码如下,流程为,收到从js收到的view id开始,通过jsview的句柄(注1)的ListenerToAckEvent接口(注2),注册监听回调(注意若有上一次注册的话,应该此时进行回收,规避内存泄漏),回调函数要处理的event(注3)的处理上需要有以下几个注意点:
A. stashedEvent: 酌情处理此标识为true的内容,stash内容是发生在 listenerToAckEvent 调用之前的已经发生过的Event内容。对于简单的跟进播放窗口位置的话,可以直接使用,没有顾虑。
B. 拿到的 x, y, width, height 以js的坐标系为标准的数值,需要进行转换,才能成java端屏幕坐标,以x举例,转化公式为: x / dw * JavaFrameLayout宽度,其中x和dw的值来源于Event结构,另外,此JavaFrameLayout指的是传给 MainPageProxy.onCreate 函数中的rootView。PS: 计算结果精度的取舍可根据应用现场表现进行调整。
import com.qcode.jsview.AckEventListener;
import com.qcode.jsview.JsView;
import com.qcode.jsview.shared_defined.CATEGORY_VIEW;
import com.qcode.jsview.shared_defined.TYPE_SHARED_VIEW_LAYOUT;
...
class NativeMediaInterface {
AckEventListener mEventListener;
JsView mJsView;
...
// 声明供js设置view id的接口
@JavascriptInterface
void syncViewId(String viewId) {
// 清理原引用
if (mEventListener != null) {
mEventListener.recycle();
mEventListener = null;
}
// 注册新监听
mEventListener = mJsView.listenerToAckEvent(CATEGORY_VIEW, TYPE_SHARED_VIEW_LAYOUT, viewId, (Bundle eventData)->{
// 这是来自绘制线程的直接调用,需要发送到主线程才能对UI进行调整
Looper.getMainLooper().post(()->{
if (eventData.getBoolean("stashedEvent")) {
// TODO: 此为已经发生过的事件
}
// 获取坐标和调整坐标
// 以 mMediaView 为目标调整的视频窗口举例
int containerWidth = constFrameLayoutWidth; // constFrameLayoutWidth 为外部设置
int designedMapWidth = eventData.getInt("dw");
int viewLeft = Math.floor((double)(eventData.getInt("x")) * containerWidth / designedMapWidth );
int viewTop = Math.floor((double)(eventData.getInt("y")) * containerWidth / designedMapWidth );
int viewWidth = Math.floor((double)(eventData.getInt("width")) * containerWidth / designedMapWidth );
int viewHeight = Math.floor((double)(eventData.getInt("height")) * containerWidth / designedMapWidth);
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams)mMediaView.getLayoutParams();
params.setMargins(viewLeft, viewTop, 0, 0);
params.width = viewWidth;
params.height = viewHeight;
mMediaView.setLayoutParams(params);
// 获取可视变化
if (eventData.getInt("visible") == 0) {
// hidden, 不可见
mMediaView.setVisibility(View.GONE);
} else {
mMediaView.setVisibility(View.VISIBLE);
}
// TODO: order调整,若有多个view有层级关系
int order = eventData.getInt("order");
});
});
}
...
}
{
...
// 以 mMiniApp 为 MiniApp 对象的句柄举例
// 向js注入此播放控制接口, js端可见名为 window.jNativeMedia
mMiniApp.addJavascriptInterface("jNativeMedia", new NativeMediaInterface());
...
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
其中注解:
注1: JsView句柄获取方法: [点击进入],
注2: JsView.listenerToAckEvent接口说明: [点击进入]
注3: Event格式说明: [点击进入]