2
头图

作者:玉追 & 以绳

优酷与华为长期保持着良好的战略合作关系,旨在为消费者带来优质的影音娱乐体验。鸿蒙操作系统的流转特性为多屏互动带来了全新的玩法,本文以优酷播放中心的技术储备为切入点,结合鸿蒙系统的镜像和流转特性,详细介绍了普通流转、自由视角和zoom 等核心能力在鸿蒙上的实践之路。

背景介绍

鸿蒙分布式体验

华为定义的分布式体验主要包含连续性体验和协同体验两种。

连续性体验

当用户在一个设备上发起操作,并切换到另一个设备上继续操作时,用户能够马上在新的设备上继续当前的操作。连续性体验包括任务接续和音视频接续。

协同体验

多个设备上的软件和硬件能力相互协同,作为一个整体为用户提供比单设备更加高效、沉浸的体验。协同体验包括软件协同和硬件协同。

对于优酷而言,我们已经向用户提供了如下的多屏互动功能。

  • 用户用手机观看视频到某一个时间点,然后切换到平板设备从刚才的中断时间点继续看视频
  • 用户用手机或者平板打开一个视频,然后使用投屏功能将视频投射到智慧屏或者电视盒子上,在大屏上继续观看
  • 用户在大屏上遇到登录或者支付等不方便使用大屏侧客户端完成的功能,他可以使用手机直接扫描大屏上的二维码登录或者支付。

上述功能,都是优酷客户端利用现有的“华为一碰传”,“华为HiPlay”等功能可以直接实现的。我们希望借助鸿蒙OS提供的特色功能,实现若干当前优酷客户端所不具备的“新功能”,开发出若干“纯鸿蒙”专有特性,然后与Android优酷主客相融合。鸿蒙代码与Android代码相互联动,共同为消费者提供各种酷炫的影音娱乐功能。

优酷客户端横跨Android,iOS,iPad,OTT等多种软硬件平台,彼此之前可以通过投屏等功能相互联动,天然具有多屏互动的场景。

而HarmonyOS采用了多种分布式技术,使得应用程序的开发实现与不同终端设备的形态差异无关。这能够让开发者聚焦上层业务逻辑,更加便捷、高效地开发应用。

在优酷鸿蒙版本中,我们基于HarmonyOS提供的分布式总线能力,与优酷端既有的投屏功能相结合,开发出全新的HarmonyOS多屏互动FA,提供多台鸿蒙设备之间多屏互动的功能。

这个FA也是100%利用鸿蒙API编写的,用户可以通过优酷鸿蒙版的播放页“视频流转”按钮,将手机端正在播放的视频流转到其它鸿蒙设备上(鸿蒙智慧屏,平板等),并可以将手机作为遥控器对大屏设备进行播控控制。

不仅能对大屏的音量、快进快退、播放速度、清晰度、剧集进行控制,还可以旋转大屏上的自由视角视频的角度。

“基于HarmonyOS的多屏互动”与传统投屏的最大区别是,我们是利用HarmonyOS提供的“设备/服务发现机制”来搜索对端设备,使用HarmonyOS的“建立连接”功能来建立设备之间的双向通信。

由于HarmonyOSOS的 “设备/服务发现机制”“建立连接”功能经过高度优化,我们自己的使用体验是“HarmonyOS多屏互动”相比传统DLNA或者Miracast镜像功能,设备发现快,连接建立快,且连接建立之后非常稳定,不容易断连。

而且,我们可以通过此连接进行高速的数据传输,直接从操控侧streaming视频到大屏端。

下面,我们简单介绍下HarmonyOS的分布式总线能力是如何与优酷既有的投屏功能相结合的。

投屏业务概览

投屏技术在我们日常生活和工作中得到了越来越多的应用,手机、平板、个人电脑等无线屏显和扩展功能也给家庭、企业会议、产品发布、远程培训带来了更加便捷、更加高效的模式变革。市场上相应的产品解决方案也是百花齐放,例如苹果公司的 Airplay 无线投屏解决方案、WiFi 联盟的 Miracast 无线投屏解决方案、谷歌公司的 ChromeCast 无线投屏解决方案、英特尔公司的 WIDI 无线投屏解决方案等,都在不同程度上推动了家庭和办公场合多屏互动产品的升级演化。

无线投屏技术和网络环境的改善其实已经标志着高效智能时代的来临,人们可以使用手机上网、看电视、娱乐、办公,智能电视除了看电视外,更趋向于智慧屏、显示屏的概念。大家希望能在电视上浏览信息、购物、玩游戏等等。人们更希望在大屏上享受看电视的沉浸感。未来的发展趋势同样也很明显,市场巨大的潜在需求是投屏技术的最大推动力,将会呈现一个百花齐放的“盛景”。

作为国内互联网视频巨头之一,优酷在投屏业务场景积累了丰富的技术和产品经验,并取得了非常迅速的发展;另外,优酷独有的播放能力,例如自由视角、zoom、AI超分、帧享4K等,也给多屏互动场景提供了强有力的扩展空间。整体而言,不管是优酷的投屏业务还是播放能力,都为同华为的合作打下了坚实的基础。

鸿蒙流转场景

HarmonyOS 是新一代的智能终端操作系统,为不同设备的智能化、互联与协同提供了统一的语言,并带来简洁、流畅、连续、安全可靠的全场景交互体验。HarmonyOS 自诞生之初就以 “万物互联” 为使命,通过一套代码和核心的分布式技术,满足了不同平台上各种硬件的需求,打通了手机、平板、电脑、汽车和智能穿戴等多种设备,完美实现了不同设备之间的流转。可以说,“流转” 是鸿蒙生态最为核心的特性之一。

随着智能时代的到来,流转无处不在,不管是车机上的双向控制,还是智能家居间的一触即连;不管是办公室的文件一碰投,还是手机和智慧屏的超级联结,有屏幕的地方就能实现流转。另一方面,5G 时代重新定义了大众的文化娱乐方式,更大的屏幕、更高的码率、更智能的流转成为了核心诉求,在这个背景下,鸿蒙跟优酷自然而然走到了一起。

优酷投屏能力版图

优酷投屏能力不仅能覆盖传统的局域网协议,如 DLNA、AirPlay 等,还全新自研了云投屏协议,彻底打破了局域网组播的限制,提高了设备发现成功率。此外,为了增加全新玩法,最新的魔屏设备附赠了 NFC 贴纸,实现了一碰投功能,引入了全新的玩法。接下来详细介绍下优酷投屏的能力版图。

能力版图

无线投屏有三个最基本的要素:内容、设备和协议。其中,内容可以是非实时的数据文件,例如电影、图片、音乐等,也可以是实时的数据流,例如屏幕镜像、直播流等。设备包括发起投屏的内容提供方(简称“发送端”)和接收投屏的内容呈现方(简称“接收端”),其中常见的发送端包括手机、平板等移动设备,负责获取远端或本地存储的媒体资源,并传输给接收端;接收端则负责响应来自发送端的内容和播控指令,并完成后续的内容呈现。协议则提供了发送端和接收端之间的交互规约,覆盖了包括设备发现、数据传输、播放控制等核心链路,比较有代表性的协议包括局域网投屏场景的 SSDP 和 SOAP 协议、云投屏场景的 MTOP 和 ACCS 协议等。

多屏互动业务复杂度高,交互链路长,横跨大小屏两条业务线,涉及到包括广告、会员、运营商、播放、硬件、媒资等非常多的业务方。

经过大量需求的长期打磨,优酷最终形成了如下的多屏互动版图,可以分为投屏协议层,投屏播控层,业务适配层等核心组成部分:

投屏协议层

1、局域网投屏。包括 DLNA 和 AirPlay,通用性较强,但受限于组播能力,常会出现发现不了设备的问题;

2、云投屏。云投屏是为了解决局域网协议无法发现设备而自研的新协议,能有效提升设备发现成功率,但受限于自营的大屏端应用,通用性受到了较大的限制;

3、NFC投屏。作为魔屏新硬件宣推的新特性之一,NFC投屏配合附带的贴纸,缩短了移动端投屏的操作链路,实现了优酷视频的 “一碰投”。

投屏播控层

  1. DRM管理。负责投屏设备的管理和校验,保证版权视频的正确投放;
  2. 格式处理。适配和兼容不同的大屏设备,保证投屏后的播放体验;
  3. 清晰度管理。负责多路清晰度的自适应和异常降级,兼顾广告等业务场景;
  4. 辅助调度。基于投屏的专属域名,具备针对不同区域、运营商、设备信息的动态流管理和调度能力。

业务适配层

  1. 投屏广告。包括内投内、内投外、外投内三种组合。此外,大小屏端上投屏广告的投放系统也在开发中;
  2. 点直短融合。将点播、直播、短视频三个场景的播放适配模块下沉至投屏内部维护,这对于后期监控链路的统一和投屏能力的外发,都有重要意义。

鸿蒙系统初实践

普通流转

“鸿蒙流转” 指利用了鸿蒙操作系统特性的多屏互动方式,与传统投屏协议最大的区别在于对鸿蒙操作系统的依赖。具体而言,鸿蒙操作系统不仅提供了 “设备/服务发现机制” 作为设备发现的协议,还提供了 “设备连接能力” 支持设备之间进行双向通信。

类似传统的投屏协议,鸿蒙流转包括基于URL的普通流转和基于流传输的流转。普通流转是指基于鸿蒙传输能力的多屏互动方式,不同于 DLNA 等通用协议,鸿蒙提供了包括设备发现、数据传输、应用查询、文件分享等核心能力,不仅能提供较原生协议而言更稳定的连接,还能按照业务诉求,将自研的APK(例如酷喵)通过小屏传输至大屏并唤起,实现一系列定制化操作。

先围绕普通流转进行介绍,其时序图如下所示。

如上图所示,对其中部分核心流程的实现进行简单介绍。

鸿蒙设备发现

跟 DLNA 投屏和云投屏发现流程完全一致,鸿蒙的发现协议在 DlnaDevs 的 search 方法中实现:

// 鸿蒙search
    if (HarmonyCastMgr.haveInst()) {
        HarmonyCastMgr.getInst().searchDevs();
        // 自由视角search
        if (HarmonyCastMgr.getInst().hasMirr()){
            HarmonyCastMgr.getInst().searchHarmonyMirrorDevs();
        }
    }

再来看看鸿蒙发现流程的实现细节:

    // 搜索设备(异步方式)
    public boolean searchDevs() {
        boolean ret = false;
        if (isHarmonyEnable()) {
            Intent intent = new Intent();
            ComponentName componentName = new ComponentName(PackageContant.PACKAGE_YOUKU, "com.youku.feature.MiddlewareAbility");
            intent.setComponent(componentName);
            intent.putExtra(AbilityUtils.PARAM_KEY_INSTALL_ON_DEMAND, true);
            intent.setAction("action.videoplayer.getdevicelist");
            try {
                mActivity = YoukuContext.getTopActivity();
                ret = AbilityUtils.connectAbility(mActivity, intent, mServiceConnection);
            } catch (RuntimeException e) {
                e.printStackTrace();
                ret = false;
            }
        }
        return ret;
    }

鸿蒙发现协议通过 AIDL 依赖了 FA 的通道,其中 mServiceConnection 内部对 IBinder 对象进行了解析,映射为多屏互动定义的 Device 类型,并回调设备列表的更新接口,核心实现如下:

   public ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            LogEx.d(tag(), "onServiceConnected");
            HarmonyRCS rcs = new HarmonyRCS(service);
            String[] devices = null;

            try {
                devices = rcs.getDeviceList();
            } catch (RemoteException e) {
                e.printStackTrace();
            }

            // 设备ID和名称数组,以适应鸿蒙AIDL查询格式
            List<String> deviceIdList = new ArrayList<>();
            List<String> deviceNameList = new ArrayList<>();

            for (int i = 0; i < devices.length; i++) {
                if ((i & 1) == 0) {
                    deviceIdList.add(devices[i]);
                } else {
                    deviceNameList.add(devices[i]);
                }
            }

            // 原始数据映射为Client格式
            if (deviceNameList.size() >= deviceIdList.size()) {
                mHarmonyDevs.clear();
                for (int i = 0; i < deviceIdList.size(); i++) {
                    String deviceId = deviceIdList.get(i);
                    String deviceName = deviceNameList.get(i);
                    if (StringUtils.isNotBlank(deviceId) && StringUtils.isNotBlank(deviceName)) {
                        Client client = new Client();
                        
                        ...

                        mHarmonyDevs.put(deviceId, client);
                    }
                }

                // 判断大屏设备是否在线,如果不在线,则停止投屏
                if (DlnaApiBu.api().proj().stat() == DlnaPublic.DlnaProjStat.PLAYING) {
                    Client currentDev = DlnaApiBu.api().proj().req().mDev;
                    if (currentDev != null && currentDev.isHarmonyDev() && !mHarmonyDevs.containsKey(currentDev.getDeviceUuid())) {
                        stopProjEx();
                    }
                }

                // 回调调用方获取设备列表
                if (null != mRcsCallback) {
                    mRcsCallback.onHarmonyDevsChanged();
                }
            }

            try {
                if (mActivity != null) {
                    AbilityUtils.disconnectAbility(mActivity, mServiceConnection);
                }
            } catch (Exception pE) {
                pE.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            LogEx.d(tag(), "onServiceConnected");
        }
    };

选择设备并启动FA

类似云投屏自研协议,鸿蒙普通流转依赖单独的发现流程,类似于其他协议,创建 HarmonyCastTrunkBiz 以作为鸿蒙协议管理类,并实现了如下基础接口:

    // 开始投屏
    void start();
  
    // 停止投屏
    void stop();

    // 发送播放指令
    void play();

    // 发送暂停指令
    void pause();

    // 发送seek指令
    void seek(int prog);

    // 发送设置音量指令
    void setVolume(int volume);

由于鸿蒙 FA 提供了定制的遥控器界面和双向数据传输通道,因此所有发送指令的任务均由鸿蒙 FA 接管,上述负责发送指令的接口仅仅同步了小屏端的播放状态。用户点击选中鸿蒙设备后,触发 start 方法并启动鸿蒙协议入口,由 HarmonyCastMgr 的 startHarmony 负责同鸿蒙 FA 之间的数据交互:

    // 投屏所需的播放数据
    HarmonyCastData castData = new HarmonyCastData();
    castData.mDev = req.mDev;
    castData.mUrl = req.mUrl;
    castData.metaData = DlnaMetadata.getInst().getMetadataWithReq(req);
    castData.isFromNowbar = isFromNowbar;

    // FA遥控器所需的UI数据
    HarmonyParameterBean parameterBean = new HarmonyParameterBean();
    parameterBean.deviceUuid = req.mDev.getDeviceUuid();
    parameterBean.mShowTitle = req.mShowTitle;
    parameterBean.projSource = "";
    parameterBean.isYoukuApp = req.mDev.isYoukuApp;
    String parameterData = JSON.toJSONString(parameterBean);
    
    // 按FA数据格式进行封装
    String touchuanData = JSON.toJSONString(castData);
    HarmonyPaBean paBean = new HarmonyPaBean();
    paBean.touchuanData = touchuanData;
    paBean.parameterData = parameterData;
  
    final String harmonyJson = JSON.toJSONString(paBean);
    Activity activity = YoukuContext.getTopActivity();
    Intent jIntent = new Intent();
    ComponentName component = new ComponentName(PackageContant.PACKAGE_YOUKU, "com.youku.feature.MainAbility");
    jIntent.putExtra(AbilityUtils.PARAM_KEY_INSTALL_ON_DEMAND, true);
    jIntent.putExtra("harmonyJson", harmonyJson);
    jIntent.setComponent(component);

    try {
        // 每次开始投屏前检测一次远端是否在线,避免因大屏掉线导致的投屏失败
        boolean remoteValid = true;
        if (HarmonyCastMgr.haveInst()) {
        remoteValid = HarmonyCastMgr.getInst().searchDevs();
    }

        if (remoteValid) {
            AbilityUtils.startAbility(activity, jIntent);
        } else {
            stopProjEx();
        }
    } catch (Exception e) {
        e.printStackTrace();
    }

自由视角

自由视角由阿里文娱摩酷实验室研发,是一种可以随意滑动视频、自由切换观看视角的播放技术。自由视角视频是多视角直播的进一步发展,带给用户更多的主动性。用户可以在观看球赛时旋转视角,并放大局部画面,近距离观看偶像球员的灌篮动作,实现堪比跨次元的真实观影体验。

以下是 “这就是街舞” 综艺的自由视角效果:

视频请见原文中:优酷鸿蒙开发实践|多屏互动开发实践

不同于普通视频,自由视角视频的码率普遍偏高。例如上述街舞录制现场,总共安装了超过 40 台专用相机,形成的阵列采集画面全方位记录了每个精彩瞬间,并实现毫秒级同步和远程协同控制,采集到的画面经过焦点矫正和3D重建,再基于各机位采集到的纹理、深度、姿态等参数信息,最终将多角度分散的画面拼接成一个高达6K、8K的高清视频文件。

因此,自由视角视频在提升用户观感的同时,也对播放端的网络环境和解码性能提出了非常高的要求。最初的自由视角实现方案是基于流转特性,小屏端将自由视角视频的 M3U8 地址或播控指令,通过上述流转能力传递给大屏侧,由大屏侧完成数据的下载、解码和渲染等后续流程。但考虑到华为智慧屏的硬件性能不足以支撑这种规模的计算量,传统的流转方案不得不被叫停。

为此,开发团队对比了鸿蒙系统上现存的所有投屏协议,得到如下表的结论:

鸿蒙投屏协议优点缺点
DLNA标准成熟协议,通用性强原生扩展能力较弱
云投屏不依赖局域网,扩展性强通用性较差
鸿蒙协议系统适配度高,扩展能力强依赖鸿蒙系统
Cast+协议系统适配度高,兼容性强,满足流传输需要依赖华为生态

经过内部评估以及同华为研发团队的讨论,开发团队最终决定放弃传统的基于URL的投屏方式,采用基于华为 Cast+ 的镜像投屏方案,具体而言,是在手机端正常播放和操控自由视角视频,并把播放器视图通过Cast+镜像到电视端。

简单介绍下华为 Cast+ 协议的实现细节。如下图所示,华为 Cast+ Kit 是华为自研的、以手机为中心的多屏协同组件,业务方可据此实现多设备之间快速、稳定、低时延的镜像传输。

基于华为 Cast+ 协议,自由视角场景的投屏链路跟普通投屏协议大体保持一致,重叠的部分包括设备搜索、选择设备和建连,不同之处在于,自由视角视频最后一步并没有投放播放地址到大屏,而是创建虚拟屏并完成显示,虚拟屏的创建时机和方式是实践过程中的重中之重。

自由视角流转方案的本质,可以概括为两个方面:首先,将遥控器页面作为主屏幕,打通其与播放页之间的双向数据通道,以实现播放控制和状态同步;其次,在虚拟屏上启动播放相关的视图,作为镜像和传输到大屏的内容。

主屏幕的方案基本确定,轮到虚拟屏的时候却踩了坑。最初能想到的虚拟屏渲染对象,自然而然是全屏播放页,然而试了才发现,直接将全屏播放页渲染到虚拟屏,会存在如下两个问题,附带分析报告如下:

问题1:主屏幕上的遥控器页面首先被镜像到大屏。

  • 原因:Cast+ 会在建连成功后、设备处于 PLAYING 状态时,开始抓取虚拟屏的内容,如果虚拟屏尚未加载,会截取主屏幕内容作为兜底。经过测试,这个时间差大概在 100ms 左右,而期间播放页无法完成初始化,因此会存在主屏幕内容占用的问题。

问题2:播放页进入后存在横竖屏切换逻辑,导致镜像后屏幕会经历一次转屏。

  • 原因:属于播放页自适应逻辑,初始进入时为竖屏,在镜像后会触发横屏切换逻辑,从而导致大屏侧的转屏。

内部综合评估后,决定采用更加轻量的方式实现虚拟屏。我们发现,播放器 Dialog 中只包含 SurfaceView 和 Loading 状态等内容,如果仅仅镜像 Dialog 级别,虽然增加了较多的播放器处理逻辑,但保证了上屏速度和更快的响应效率。调整后的方案结构如下图所示。

该方案具备如下优势:

  1. 优化了播放器的生命周期,每次在自由视角流转和小屏播放之间切换时,仅仅切换 SurfaceView,并不重建播放器,此时播放器只是处于 STOPPED 状态,切到小屏播放后,跳过了初始化和播放地址请求等过程,保证快速起播;
  2. 将启动 Presentation 的时机提前,在收到 onDisplayAdded 时就进行页面启动和播放器续播,大大缩短了上屏时间。

综上所示,最终的效果如下视频所示。

视频请见原文中:优酷鸿蒙开发实践|多屏互动开发实践

线上表现

网友们对鸿蒙系统的特性和优酷鸿蒙版本的表现,也表现出了强烈的兴趣,我们摘取了网上的一些体验视频,列举如下:

随后,经过两个版本的持续迭代,优酷鸿蒙版的稳定性和体验得到了极大改善。另一方面,伴随着鸿蒙系统的自我完善,优酷鸿蒙版的二期需求版本已经开始排期,相信在双方强强联合之下,会持续给市场和用户带来惊喜。

总结

在优酷鸿蒙版多屏互动专项的开发过程中,各参与方在鸿蒙接口和调试环境尚不完善的时候介入,克服了重重困难,不断探索试错,在极短的时间内掌握了鸿蒙开发技术栈,不仅顺利完成了大大小小的开发任务,还反馈给华为技术团队不少遗留问题和改进建议。这是双方再一次的深度合作,也是一次非常成功的实践。

对于华为而言,优酷鸿蒙版不仅给鸿蒙生态带来了开创性的视频新交互形式,让新用户大呼过瘾;而且完美适配了鸿蒙系统流转特性、为后续更丰富的新玩法铺平了道路。对于优酷而言,鸿蒙上的多屏互动实践,大大扩展了现有的多屏互动能力版图,为鸿蒙新时代积累了重要经验。

关注我们,每周 3 篇移动技术实践&干货给你思考!


阿里巴巴终端技术
336 声望1.3k 粉丝

阿里巴巴移动&终端技术官方账号。