Skip to content

4.2 IPN接口使用

iPanel TV软终端APK支持的私有JS功能接口

4.2.1 IPNBridge对象

4.2.1.1 属性

  • 支持属性常量

    javascript
        {
            //RN core版本号
            coreVer: string, // Base bundle version
    
            //Homed related
            token: string, // login token
            deviceNo: string, // login deviceno
            host: string, // homed host e.g. homed.tv
            smartCard: string, // ca 卡号
            mac: string, // MAC 地址
            ip: string, // IP 地址
            deviceType: string, // login deviceType
            uid: string, // homed userId
            homedLogin: object, // 完整登录信息json对象
            
            
            //iPanelCloud related
            deviceId: string, // TV ID
            appId: string, // APP ID
            oid: string, // dongle 鉴权后分配的设备ID
            caId: string, // dongle 鉴权后分配的CAID
            appKey: string, // APP KEY
            operatorId: string, // Operator UUID
            areaId: string, // 用户选择的地址区域ID
            cloudHost: string, // ipanelCloud 域名 e.g. ipanelcloud2.ipanel.cn
            cloudUid: string, //  iPanelCloud userId
            roomInfo: object, // 电视所在房间信息:https://git.ipanel.cn/git/liubob/ApiDoc/src/master/accountsrv/2_1_0_accountApi.md#112-roominfo-%E5%AF%B9%E8%B1%A1 
            loginInfo: object, // Homed授权登录信息, result对象:https://git.ipanel.cn/git/liubob/ApiDoc/src/master/accountsrv/2_1_0_accountApi.md#26-homed%E6%8E%88%E6%9D%83%E7%99%BB%E5%BD%95
    
            //播放器相关
            player: string, // 当前版本的播放器类型 dvb, ijk, ctc
    
            //系统相关
            sysUser: string // 应用是否有系统权限
        }
  • 属性使用说明

      1. IPNBridge属性是常量。并不是所有属性一定能取到值,涉及接口交互的数据(如homed登录相关和cloud登录相关)就存在取不到的情况。
      1. IPNBridge对象在RN引擎启动时创建。如果此时homed和cloud还未登录则相关信息都取不到。
      • theme可以配置RN引擎在apk启动过程中(提前)预加载 homed和cloud用户大概率未登录。由于RN引擎在homed和cloud登录前就已经加载完成。IPNBridge对象下属性已经生成。原生后面不会再赋值重写。经常出现进入RN应用后取不到homed和cloud相关信息。
      • theme可以配置RN引擎在homed登录后再预加载 基本上可以保证homed和cloud相关数据获取正常。但是也存在登录失败的情况。进入RN应用后,必须要homed或者cloud相关数据,则还是需要做重新登录相关流程。这就涉及到IPNBridge的方法和事件功能的调用支持。
      1. 常用实例
      javascript
      import { View, Text, StyleSheet } from 'react-native'
      import React from 'react'
      import IPNBridge from '../../../native/IPNBridge';//相对路径可调整
      
      export default function IPNBridge_prop() {
          const data = [
              { name: 'coreVer', value: IPNBridge.coreVer },
              { name: 'deviceId', value: IPNBridge.deviceId },
              { name: 'token', value: IPNBridge.token },
              { name: 'host', value: IPNBridge.host },
              { name: 'cloudUid', value: IPNBridge.cloudUid },
              { name: 'appKey', value: IPNBridge.appKey },
              { name: 'operatorId', value: IPNBridge.operatorId }
          ]
          return (
              <View style={styles.app}>
              {
                  data.map((item, index) => {
                      return (<View key={'list' + index} style={styles.list}>
                          <Text style={styles.name}>{item.name + ':'}</Text><Text style={styles.value}>{item.value}</Text>
                      </View>)
                  })
              }
              </View>
          )
      }
      const styles = StyleSheet.create({
          app: {
              alignItems: 'center',
              justifyContent: 'center',
          },
          list: { flexDirection: 'row', width: '80%', marginVertical: 5 },
          name: { color: '#fff' },
          value: { color: '#ff0' }
      })

4.2.1.2 方法

  • 支持功能方法

    javascript
        /**
            * 打印日志到logcat
        * @param msg
        */
        @ReactMethod
        function log(String msg)
    
        /**
            * 通过intent uri 进行原生页面跳转
        * @param intent
        */
        @ReactMethod
        function startIntent(String intent)     
    
        /**
            * 跳转homed返回的媒资信息
        * @param program homed的媒资信息对象
        */
        @ReactMethod
        function openProgram(ReadableMap program)
        
        /**
            * 执行函数并异步回调结果
        * @param func func uri
        * @param promise
        */
        @ReactMethod
        function evalFunc(String func, Promise promise)
    
        /**
            * 发送Otto消息到Java层
        * e.g. 发送设备蓝牙操作指令
        * clsName com.ipanel.join.homed.stb.otto.OttoBleCmd
        * data {
        * 'mac': 'aa-bb-cc-dd-ee', //设备mac
        * 'cmd': '{"cmd\":"start"}' //json格式的字符串, 具体指令不同设备不同
        * }
        * e.g. 发送消息到playcube模块
        * clsName com.ipanel.join.homed.stb.otto.OttoMsgToArt
        * data {
        *  'name': '',
        *  'message': ''
        * }
        * @param clsName 原生消息类
        * @param data 消息数据结构,需与原生类匹配
        */
        @ReactMethod
        function sendOttoMsg(String clsName, ReadableMap data)
    
        /**
            * 将指定按键事件发送到JS处理,处理完成后JS必须调用onKeyEventResult通知结果
        * [
        * 'KEYCODE_MENU'
        * ]
        * @param array 键值名称列表
        */
        @ReactMethod
        function setEmitKeyEvents(ReadableArray array)
    
        /**
            * 按键事件处理结果
        * @param handled 已处理,原生无需再处理
        * @param id 按键id
        */
        @ReactMethod
        function onKeyEventResult(boolean handled, int id)        
    
        
        /**
            * 退出应用界面
        */
        @ReactMethod
        function exitApp()
    
        /**
            * 读取app kv 属性
        * @param key
        * @return
        */
        @ReactMethod(isBlockingSynchronousMethod = true)
        function appKVGet(String key)
    
        /**
            * 设置app kv 属性
        * @param key
        * @param value
        */
        @ReactMethod(isBlockingSynchronousMethod = true)
        function appKVSet(String key, String value)
    
        /**
            * 读取系统属性
        * @param key
        * @return String
        */
        @ReactMethod(isBlockingSynchronousMethod = true)
        function sysPropGet(String key)
        
        /**
            * 设置系统属性
        * @param key
        * @param value
        */
        @ReactMethod
        function sysPropSet(String key, String value)
    
        /**
            * 读取app 属性
        * @param key
        * @return String
        */
        @ReactMethod(isBlockingSynchronousMethod = true)
        function appPropGet(String key)
    
        /**
            * 设置app属性
        * @param key
        * @param value
        */
        @ReactMethod
        function appPropSet(String key, String value)    
    
        /**
            * Homed 登录参数
        * @return 同 IPNHomedLogin 事件(异步响应Promise promise)
        */
        @ReactMethod
        function getHomedLogin()
    
        /**
            * Http 代理数据回应
        * @param id        唯一请求id
        * @param status    http 状态码 如 200, 404
        * @param statusDes http 状态描述 如 OK, NOT FOUND
        * @param mime      content类型, 如 application/json, text/plain
        * @param text      返回内容
        */
        @ReactMethod
        function notifyProxyResp(long id, int status, String statusDes, String mime, String text)
    
        /**
            * 清除未完成的图片加载任务
        */
        @ReactMethod
        function clearImageTasks()
        
        /**
            * 获取系统安装的原生apk列表
        * @return Promise promise(异步)
        */
        @ReactMethod
        function getNativeApps()
  • 方法使用说明

      1. IPNBridge.log输出只能到logcat日志;metro服务器实时日志只能通过console.log输出
      1. 返回Promise对象的代表是异步方法回调返回结果。如:IPNBridge.evalFunc()、IPNBridge.getHomedLogin()。调用前要加await,并且所属函数定义需要加async。
      1. 常用用例
    javascript
            import { Text, StyleSheet, Dimensions, NativeModules, NativeEventEmitter, ToastAndroid } from 'react-native'
            import React, { Component } from 'react'
            import IPNBridge from '../../../native/IPNBridge';
            import IPNView from '../../../native/IPNView';
            import IPNImage from '../../../native/IPNImage';
    
            const data = [
                {
                    name: '搜索',
                    iconImg: {
                        normal: 'icon_top_search_normal',
                        focus: 'icon_top_search_focus',
                    },
                    intent: 'intent://?action=host.OPEN&es__target_=search', //跳转地址
                },
                {
                    name: '模式',
                    iconImg: {
                        normal: 'icon_top_mode_normal',
                        focus: 'icon_top_mode_focus',
                    },
                    intent: 'func://com.ipanel.join.homed.stb.ModeSwitchPopup.open',
                },
                {
                    name: 'VIP',
                    iconImg: {
                        normal: 'icon_top_vip_normal',
                        focus: 'icon_top_vip_focus',
                    },
                    intent:
                    'intent://send_broadcast/?action=ipanel.bg.ACTION&es_action=local&es_url=func://com.ipanel.join.homed.stb.pay.PaymentManager.show',
                },
                {
                    name: '设置',
                    iconImg: {
                        normal: 'icon_top_setting_normal',
                        focus: 'icon_top_setting_focus',
                    },
                    type:1
                }
            ]
            const utils = {
                WIDTH: Dimensions.get('window').width,//应用显示屏幕宽度
                HEIGHT: Dimensions.get('window').height,////应用显示屏幕高度
                px: (size, pageWidth = 1920) => {//按1920设计图尺寸进行开发转换
                    return Math.round(size * (utils.WIDTH / pageWidth));
                }
            }
    
            export default class TopEntranceList extends Component {
                constructor(props) {
                    super(props);
                    this.state = {
                    };
                }
                //组件挂载
                componentDidMount() {
                    IPNBridge.setEmitKeyEvents([//注册按键捕获:按键索引需要找旭哥定义的按键映射规则
                        'KEYCODE_MENU', //菜单键
                        'KEYCODE_HOME', //主页键(捕获不到)
                        // 'KEYCODE_BACK', //返回键
                        'KEYCODE_DPAD_RIGHT', //右键
                        'KEYCODE_DPAD_LEFT', //左键
                        'KEYCODE_DPAD_UP', //上键
                        'KEYCODE_DPAD_DOWN', //下键
                        'KEYCODE_ENTER', //确认键
                        'KEYCODE_DPAD_CENTER', //确认键
                    ]);
                    const nativeToRNEmitter = new NativeEventEmitter(NativeModules.IPNBridge);
                    this.nativeKeyEvent = nativeToRNEmitter.addListener(
                    //按键监听
                    'IPNKeyEvent',
                    (event) => {
                        console.log('IPNKeyEvent---event=', event);
                        if (event.action === 0) {//0:keydown,1:keyup
                        switch (event.name) {
                            case 'KEYCODE_MENU':
                            IPNBridge.onKeyEventResult(true, event.id);//通知apk不要处理菜单键逻辑,RN处理
                            ToastAndroid.showWithGravity('您按菜单键了', ToastAndroid.SHORT, ToastAndroid.CENTER);//弹提示信息
                            break;
                        }
                        }
                    })
                }
                //组件销毁
                componentWillUnmount() {
                    IPNBridge.setEmitKeyEvents([]);//取消按键捕获注册
                    this.nativeKeyEvent?.remove();//清理按键监听事件
                }
                // 获取homedLogin数据:异步函数
                async getHomedLogin() {
                    let result = await IPNBridge.getHomedLogin();
                    if (result) {
                        ToastAndroid.showWithGravity(JSON.stringify(result), ToastAndroid.SHORT, ToastAndroid.BOTTOM);//弹提示信息
                    }
                }
                render() {
                    return (
                    <IPNView
                        style={styles.top}
                        blocked_edges={['left', 'right']}//设置焦点区域内最左或者最右不能继续往左或右移动
                        focus_finder={true}
                        focus_on_selected_first={true}
                        focus_selector={{//设置焦点图片样式
                            theme_focus: 'rrect_r30',
                            on_top: true,
                            force_front: true,
                            zoom: 0,
                        }}
                        focus_tag={3}>
                        {data.map((item, index) => {
                        return (
                            <IPNView
                            key={'topleftbtn' + index}
                            focusable={true}
                            requestTVFocus={index ? false : true}//设置默认焦点,true获焦
                            onFocus={() => { //获得焦点触发事件
                                IPNBridge.log('onFocus--IPNBridge.log--index=' + index)//只能输出到logcat日志,metro服务器日志看不到
                                console.log('onFocus--console.log--index=' + index)//logcat和metro服务器日志都可以看到
                            }}
                            onClick={() => { // 确定键触发事件
                                if (item.type === 1) {
                                    this.getHomedLogin();
                                } else if (item?.intent) {
                                    IPNBridge.startIntent(item?.intent);
                                }
                            }}
                            onBlur={() => { //失去焦点触发事件
                                console.log('onBlur---index=' + index)
                            }}
                            style={styles.btnBox}
                            state_bg={{
                                gradient: {//背景渐变色
                                normal: {//常态
                                    colors: [
                                    'rgba(255,255,255,0.08)',
                                    'rgba(255,255,255,0.08)',
                                    ],
                                    orientation: 'left_right',
                                    cornerRadius: utils.px(30),
                                },
                                focus: {//获焦
                                    colors: ['#667EFF', '#BE52EB'],
                                    orientation: 'left_right',
                                    cornerRadius: utils.px(30),
                                },
                                },
                            }}>
                            {item.iconImg ? (
                                <IPNImage
                                style={{ width: utils.px(36), height: utils.px(36) }}
                                source={{
                                    theme: {
                                        normal: item.iconImg.normal,
                                        focus: item.iconImg.focus,
                                    },
                                    default: 'theme',
                                }}
                                />
                            ) : null}
                            <Text style={styles.btn}>{item.name}</Text>
                            </IPNView>
                        );
                        })}
                    </IPNView>
                    );
                }
            }
            const styles = StyleSheet.create({
                top: {
                    position: 'absolute',
                    top: 0,
                    height: utils.px(100),
                    flexDirection: 'row',
                    alignItems: 'center',
                    marginLeft: utils.px(48),
                },
                btnBox: {
                    height: utils.px(60),
                    alignItems: 'center',
                    justifyContent: 'center',
                    flexDirection: 'row',
                    paddingHorizontal: utils.px(24),
                    marginRight: utils.px(24)
                },
                btn: {
                    fontSize: utils.px(30),
                    color: 'rgba(255,255,255,0.6)',
                    textAlignVertical: 'center',
                    paddingLeft: utils.px(8)
                }
            }) 
        ```

4.2.1.3 事件

  • 支持全局事件

    javascript
        /**
         * <pre>
         * state 0xCB - 实时数据指令, 0xCC - 返回结果指令
         * hasHB 心跳 (只有 0 或 1)(当为 1 时检测到心跳,0 时没有检测到心跳)
         * press 压力值
         * systolic  收缩压(高压)(单位 mmHg)
         * diastolic 舒张压(低压)(单位 mmHg)
         * pulse 心率值
         * heartAnomaly 心率不齐 (0 或者 1)(1 表示心率不齐)
         * </pre>
         */
        String HEALTH_BPM = "IPNHealthBPM";
    
        /**
         * <pre>
         * deviceType 设备类型:CF 表示脂肪秤,CE 表示人体秤,CB 表示婴儿秤,CA 表示厨房秤
         * weight 重量,单位 kg
         * state HR state 0x80 正在测试, 0xC0 测试完成
         * heartRate 次/分钟
         * resistent 加密电极阻抗
         * </pre>
         */
        String HEALTH_WEIGHT = "IPNHealthWeight";
    
        /**
         * <pre>
         * state 0 未连接, 1 已连接, 2 连接异常
         * type 0 idu,  3 smit
         * rcState 0 离线,1 在线
         * rcType 0 ble, 1 IOT
         * rcBattery 电量百分比
         * rcCharge 0 未充电, 1 充电中, 2 已充满
         * otherState 其他蓝牙外设 0 未连接, 1 已连接
         * earphoneState 蓝牙耳机 0 未连接, 1 已连接
         * </pre>
         */
        String DONGLE_STATE = "IPNDongleState";
    
        /**
         * <pre>
         * mac 设备 mac地址
         * type 设备类型
         * data 设备数据,不同设备数据结构不同
         * </pre>
         */
        String BLE_DEVICE_DATA = "IPNBleDeviceData";
    
        /**
         * <pre>
         * 加载业务bundle时发送, 内容为bundle目录的 file uri路径。用于JS端静态资源文件的路径解析
         * e.g. file:///data/data/com.ipanel.join.homed.stb/files/react_native/tvsport/2/
         * </pre>
         */
        String BUNDLE_CHANGE  = "sm-bundle-changed";
    
        /**
         * 启动参数
         * 参数 _new_intent_ 1(仅当onNewIntent时存在)
         * 参数 _asset_root_ 本地资源根路径 e.g. file:///data/data/com.ipanel.join.homed.stb/files/react_native/tvsport/v1.1.91/
         */
        String APP_START = "IPNAppStart";
    
        /**
         * 应用状态变化
         * 参数 app_name 应用名称
         * 参数 state active/background
         */
        String APP_STATE = "IPNAppState";
    
        /**
         * Homed登录参数变化, 包含新的登录参数
         */
        String HOMED_LOGIN = "IPNHomedLogin";
    
        /**
         * iPanelCloud登录参数变化, 包含新的登录参数
         */
        String CLOUD_LOGIN = "IPNCloudLogin";
                
        /**
         * key action
         * action 0 down 1 up
         * name e.g. KEYCODE_MENU
         * code android keyCode
         * modifier ctrl,alt, shift, meta 状态位
         * id keyEvent id, for result notify
         */
        String KEY_EVENT = "IPNKeyEvent";
    
        /**
         * 具体定义见playcube文档
         * name 消息类型名称
         * message 消息数据 json 字符串
         */
        String ART_MESSAGE = "IPNArtMessage";
    
        /**
         * <pre>
         * connected true/false 连接状态
         * type     网络类型 1000 usb 代理网络
         * connectTime 网络连接的系统相对时间
         * disconnectTime 网络连接断开的系统相对时间
         * </pre>
         */
        String NET_STATE = "IPNNetState";
    
        /**
         * 支付状态信息 json字符串
         */
        String PAY_RESULT_Message = "PayResultMessage";
    
        /**
         * 原生广播
         * action 广播动作
         *   android.intent.action.PACKAGE_ADDED 新的应用安装成功
         *   android.intent.action.PACKAGE_REPLACED 已安装的应用更新
         *   android.intent.action.PACKAGE_REMOVED 应用被删除
         *   ipanel.action.INSTALL_RESULT 应用安装结果, 参数名: result 类型:boolean
         * extras  参数
         */
        String BROADCAST = "IPNBroadcast";
  • 事件使用说明

      1. 事件监听模块固定为NativeModules.IPNBridge。
      1. React-Navigation切换页面时,尽量切换前后对事件进行释放和重新监听操作。否则A页面跳转到B页面,A页面监听的事件还能收到。
      1. 常用用例
    javascript
        import { NativeEventEmitter, NativeModules } from 'react-native';
        import { name as APPNAME } from '../../package.json';//读取应用模块名
    
        let IPNAppStateEvent = null;
        /* 开启事件监听*/
        function listenerIPNAppState() {
            console.log('[IPNAppState]---listener---AppState---');
            /**
            * 应用状态变化
            * 参数 app_name 应用名称
            * 参数 state active/background
            */
            const nativeToRNEmitter = new NativeEventEmitter(NativeModules.IPNBridge);
            IPNAppStateEvent = nativeToRNEmitter.addListener('IPNAppState', (event) => {
                console.log('[IPNAppState]---IPNAppState---event=' + JSON.stringify(event));
                if (event?.app_name === APPNAME) {//每个RN应用前后台切换时都会发送该事件。需要记录本RN应用的显示状态
                    global[APPNAME]['appState'] = event?.state;
                }
            });
        }
        // 释放事件监听
        function releaseIPNAppState() {
            console.log('[IPNAppState]---releaseNativeMessage---');
            IPNAppStateEvent?.remove();
        }
    
        export { listenerIPNAppState, releaseIPNAppState };

4.2.2 返回键统一逻辑

目前RN模块是单引擎机制,当多个RN应用都在运行时,只要监听事件,都能收到同一个消息。

  • 后台运行时交给原生处理 1、前台应用如果需要处理逻辑,则会将按键截止,不会将消息流到后台应用;如果不处理留下来则就是希望原生底层去处理,返回到上一个页面场景。 2、基于IPNAppState消息每个RN应用记录当前是否在前台/后台状态。在前台时,才需处理返回键逻辑;在后台时,则需将按键交给原生处理并终止逻辑。(否则react navigation会执行导航的返回逻辑及多次执行返回逻辑问题)

  • 实例源码

    //监听返回键事件
    this.backHandler = BackHandler.addEventListener('hardwareBackPress',this.backHandlerListener);

    //返回键处理函数
    backHandlerListener = () => {
        //global是全局存储对象,APPNAME是当前应用名称,appState是状态变量
        if (global[APPNAME]['appState'] === 'background') {//在后台时
            BackHandler.exitApp();
            return true; //20240726后台运行时,直接调用原生退出方法由原生处理并return true。否则react navigation会执行导航的返回逻辑。
        }
        //其他返回键逻辑
        ...
    }

作业

  • 题目 开发一个RN应用,实现一个首页(直接用前一堂课的首页页面)和一个点播播放页,可相互跳转。
  • 要求 1、采用react-navigation导航跳转RN页面。 2、首页用上一节课实现的页面,增加确定键功能,对接点播、回看、应用和直播功能。点播节目进入点播播放页;应用节目读取extend_content.androidurl是intent://通过IPNBridge.startIntent跳转;其他节目通过IPNBridge.openProgram跳转。 3、点播播放页实现接收节目id参数和剧集标记。剧集通过接口/media/series/get_info获取剧集列表video_list中第一个单集id,再通过/media/video/get_info获取playtoken进行拼接播放地址播放;单集直接通过/media/video/get_info接口获取playtoken后拼接播放地址播放。 4、通过IPNKeyEvent事件捕获左右键,实现点播播放进度条左右按键可进行seek播放,确定键可暂停和恢复播放等基本功能。并且播放结束接着从头播放。 5、accesstoken取IPNBridge.token,verifycode取IPNBridge.deviceId 6、点播播放地址取http://拼接规则
        function joinPlayUrl(_proObj){
            let url = '';
            const urls = _proObj.demand_url.filter(item => item.includes('http://'));
            if (urls&&urls[0]) {
                url += '?protocol=http&playtype=demand&accesstoken='+IPNBridge.token+'&playtoken='+_proObj.play_token+'&verifycode='+IPNBridge.deviceId
            }
            return url;
        }
    7、播放页效果如下图 alt textalt text