鸿蒙新特性 | 跨设备流转——手机拍的视频平板接着播

一、我们想解决什么问题
生活里的三个"卡壳"瞬间
先不说技术,聊几个你一定经历过的场景。
场景一:视频接力。 你在地铁上用手机刷完了半小时的视频精华片段,回家后想舒舒服服用平板的大屏幕继续看同一个视频的后续内容。这时候你要怎么做?大概率是:打开平板 → 打开视频 App → 登录账号 → 搜索视频 → 拖进度条。步骤不少,而且如果 App 不支持登录多设备,还要忍受奇怪的提示。
场景二:文档接力。 你在手机上随手拍了一张会议白板的照片,回到工位想用电脑上的修图工具处理一下。以前的选择是:微信文件传输助手、AirDrop、或者数据线直连。每一种都有点麻烦,尤其是当你身边没有 WiFi 的时候。
场景三:导航接力。 你在车上用手机导航,快到家了但导航还在继续,下车后想把导航"甩"到手表上继续步行指引。想象一下这个画面——你拎着东西站在小区门口,手机屏幕亮了半天,定位飘来飘去,结果还得重新输入目的地。
这三个场景的共同点是:任务在一台设备上开始,但用户想在另一台设备上结束。没有跨设备流转,你就得手动"交接";有了跨设备流转,系统自动帮你把上下文传递过去,对用户来说感觉就像——视频从来没有停过。
技术上的"卡点"在哪里
好,有了需求,我们来看看技术上卡在哪里。
第一,设备发现。你要把视频传给平板,首先得知道平板在哪。手机周围可能有电视、耳机、音箱、平板……系统要能认出谁是你的"自己人",而不是邻居的设备。这需要一个可靠的设备发现机制。
第二,数据传递。视频文件动不动几百 MB,直接走蓝牙太慢,走云端太贵,最好能走局域网直连。更重要的是,传递的不只是文件本身——还有"播放到第几分钟第几秒"这样的状态信息。
第三,一致性。两台设备看到的内容要一致,手机上暂停了,平板上也得暂停;手机上切到了某个进度,平板上打开也要从同一个位置开始。
第四,权限与安全。不是所有设备都值得信任,也不是所有数据都应该被分享。HarmonyOS 通过"同一华为账号 + 同一网络"的双重校验来确保安全。
理解了这四个卡点,后面的设计你就明白为什么这么做了。
二、数据模型设计
先建模,再写代码
技术方案的第一步,永远是先把数据模型定义清楚。就像盖房子之前先画结构图,先把"有什么东西""东西长什么样"说清楚,后面的代码才有根可循。
我们不需要搞什么复杂的类继承,TypeScript 的 interface 就是干这个的——清晰、直观、够用。
设备信息:DeviceInfo
想象你走进一个会议室,里面坐了好几个人,你要先认识他们,才能决定跟谁合作。DeviceInfo 就是设备的"身份证":
// 设备信息接口
interface DeviceInfo {
deviceId: string; // 设备唯一标识,UUID 格式
deviceName: string; // 用户给设备起的名字,如"小明的小米平板"
deviceType: DeviceType; // 设备类型:手机/平板/PC/手表等
networkType: NetworkType; // 网络类型:WiFi / 5G / 离线
isTrusted: boolean; // 是否为可信设备(同账号且已授权)
batteryLevel: number; // 当前电量,0~100
}
// 设备类型枚举
enum DeviceType {
PHONE = 'phone',
TABLET = 'tablet',
PC = 'pc',
WATCH = 'watch',
TV = 'tv',
UNKNOWN = 'unknown'
}
// 网络类型枚举
enum NetworkType {
WIFI = 'wifi',
CELLULAR = 'cellular',
DISCONNECTED = 'disconnected'
}
流转会话:TransferSession
设备认识完了,接下来要建立一条"任务通道"。就像你和同事之间建一个工作群,群里定义好:这个任务从哪来、传给谁、传什么内容、进度如何。
// 流转会话接口
interface TransferSession {
sessionId: string; // 会话唯一 ID
sourceDevice: DeviceInfo; // 发送端设备
targetDevice: DeviceInfo; // 接收端设备
contentType: ContentType; // 内容类型:视频/图片/文档/链接
contentUri: string; // 内容 URI(可以是本地路径或分布式路径)
contentMeta: ContentMeta; // 内容元数据
status: TransferStatus; // 当前状态:准备/传输中/完成/失败
progress: number; // 传输进度,0~100
createdAt: number; // 创建时间戳
expiresAt: number; // 会话过期时间(避免资源泄漏)
}
// 内容元数据
interface ContentMeta {
title: string; // 内容标题
duration?: number; // 视频/音频时长(秒)
position?: number; // 播放位置(秒),用于视频续播
fileSize?: number; // 文件大小(字节)
mimeType: string; // MIME 类型
}
// 传输状态枚举
enum TransferStatus {
IDLE = 'idle',
PREPARING = 'preparing',
TRANSFERRING = 'transferring',
COMPLETED = 'completed',
FAILED = 'failed',
CANCELLED = 'cancelled'
}
内容类型:ContentType
流转的内容五花八门,笼统传一个大文件包不够细致,我们需要细分:
// 内容类型枚举
enum ContentType {
VIDEO = 'video', // 视频文件
IMAGE = 'image', // 图片文件
DOCUMENT = 'document', // 文档(PDF/Word/Excel 等)
URL = 'url', // 链接(网页/App 页面)
AUDIO = 'audio', // 音频文件
LOCATION = 'location', // 地理位置(用于导航接力)
}
小结:模型设计的思路
你可能发现了,这三个接口层层递进:DeviceInfo 解决"设备是谁"的问题,TransferSession 解决"任务怎么跑"的问题,ContentType 解决"传的是什么"的问题。先把架子搭好,后面的代码就像填空题一样简单。
三、核心设计决策
方案对比:为什么我们这样设计
技术选型不是"最先进最好",而是"最适合当前场景"。我们来看几个关键决策点。
决策一:传输协议选 SoftBus 还是 HTTP?
| 对比维度 | SoftBus(分布式软总线) | HTTP 轮询/推送 |
|---|---|---|
| 传输速度 | 内网极快(百兆文件秒传) | 依赖服务器,较慢 |
| 离线支持 | 同一华为账号可离线传递 | 必须有外网 |
| 开发成本 | HarmonyOS 原生封装,开箱即用 | 需要自建服务 |
| 适用场景 | 大文件(视频/图片) | 小数据(状态同步) |
我们的结论是:以 SoftBus 为主,HTTP 作为兜底。大文件走软总线,享受极致速度;小状态走轻量协议,保证可用性。
决策二:状态存储用本地还是分布式数据服务?
跨设备流转最怕的就是"手机停了、平板不知道从哪开始"。HarmonyOS 提供了分布式数据服务(Distributed Data Service),能让多台设备共享同一份数据,而且自动同步。
// 选型对比
const isLocalOnly = false; // ❌ 只存本地:另一台设备拿不到状态
const isCloudSync = true; // ⚠️ 云同步:慢,依赖网络,还收费
const isDistributedDB = true; // ✅ 分布式数据库:本地化速度,跨设备同步
决策三:发现设备用蓝牙还是 WiFi?
蓝牙功耗低但速度慢,适合配对阶段;WiFi 速度快,适合大数据传输。HarmonyOS 的做法是:先用蓝牙低功耗广播发现设备,再用 WiFi Direct 建立高速通道传输数据。扬长避短,效率最高。
决策四:用户体验上,推送制还是拉取制?
- 推送制(手机主动推给平板):手机控制感强,但平板可能不想收(比如正在打游戏)。
- 拉取制(平板主动从手机拉数据):平板自主可控,但需要平板主动发起。
- 我们的选择:用户确认 + 智能推荐。检测到用户在两台设备上都有同一个 App 时,弹出"是否继续在平板上观看?"的提示,让用户自己决定。
四、完整代码实现
代码文件一:设备发现服务 DeviceDiscovery.ets
这是整个流转的"入口"。我们用 HarmonyOS 提供的分布式能力,先找到附近的设备:
// DeviceDiscovery.ets - 设备发现服务
import deviceManager from '@ohos.distributedHardware.deviceManager';
class DeviceDiscovery {
private dmInstance: deviceManager.DeviceManager | null = null;
private deviceList: DeviceInfo[] = [];
// 初始化设备管理器
initialize() {
deviceManager.createDeviceManager('com.atomgit.app', (err, dm) => {
if (err) {
console.error('设备管理器创建失败:', err);
return;
}
this.dmInstance = dm;
this.startDiscovery();
});
}
// 开始发现可信设备
startDiscovery() {
if (!this.dmInstance) return;
// 获取同一华为账号下的可信设备列表
const trustedDevices = this.dmInstance.getTrustedDeviceListSync();
this.deviceList = trustedDevices.map((device: any) => ({
deviceId: device.deviceId,
deviceName: device.deviceName,
deviceType: this.mapDeviceType(device.deviceType),
isTrusted: true,
networkType: NetworkType.WIFI
}));
console.info('发现可信设备数量:', this.deviceList.length);
}
// 获取可用设备列表(过滤掉自己)
getAvailableDevices(): DeviceInfo[] {
const myDeviceId = this.dmInstance?.getLocalDeviceInfoSync()?.deviceId;
return this.deviceList.filter(d => d.deviceId !== myDeviceId);
}
private mapDeviceType(type: number): DeviceType {
const map: Record<number, DeviceType> = {
0x00: DeviceType.PHONE,
0x01: DeviceType.TABLET,
0x02: DeviceType.PC,
0x03: DeviceType.WATCH,
0x04: DeviceType.TV,
};
return map[type] || DeviceType.UNKNOWN;
}
}
代码文件二:流转服务 TransferService.ets
设备找到了,接下来就是建一条流转通道,把内容送过去:
// TransferService.ets - 跨设备流转服务
import distributedChannel from '@ohos.distributedHardware.channel';
import fileIO from '@ohos.fileio';
class TransferService {
private session: distributedChannel.DistributedChannel | null = null;
// 创建流转会话
async createSession(source: DeviceInfo, target: DeviceInfo, content: ContentMeta): Promise<TransferSession> {
const sessionId = this.generateUUID();
// 建立跨设备通信通道(走 SoftBus)
this.session = await distributedChannel.createSession({
sessionName: `transfer_${sessionId}`,
peerDeviceId: target.deviceId,
dataType: distributedChannel.DataType.FILE,
});
return {
sessionId,
sourceDevice: source,
targetDevice: target,
contentType: ContentType.VIDEO,
contentUri: '',
contentMeta: content,
status: TransferStatus.PREPARING,
progress: 0,
createdAt: Date.now(),
expiresAt: Date.now() + 30 * 60 * 1000 // 30 分钟后过期
};
}
// 传输文件(带进度回调)
async transferFile(session: TransferSession, filePath: string,
onProgress: (pct: number) => void): Promise<void> {
if (!this.session) throw new Error('会话未建立');
session.status = TransferStatus.TRANSFERRING;
// 打开本地文件,通过分布式通道发送
const fd = fileIO.open(filePath, fileIO.OpenMode.READ_ONLY);
const fileSize = fileIO.statSync(filePath).size;
let sent = 0;
const buffer = new ArrayBuffer(8192);
let bytesRead = 0;
// 分块传输,每传完一块更新进度
while ((bytesRead = fileIO.read(fd, buffer)) > 0) {
await this.session.write(buffer.slice(0, bytesRead));
sent += bytesRead;
const pct = Math.floor((sent / fileSize) * 100);
onProgress(pct);
session.progress = pct;
}
fileIO.close(fd);
session.status = TransferStatus.COMPLETED;
console.info('文件传输完成,sessionId:', session.sessionId);
}
private generateUUID(): string {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
const r = Math.random() * 16 | 0;
return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
});
}
}
代码文件三:续播接力 VideoContinuity.ets
传完文件还不够,关键是"从哪继续看"。这段代码管理播放位置的记录和恢复:
// VideoContinuity.ets - 视频续播接力
import distributedKV from '@ohos.distributedKvStore';
class VideoContinuity {
private kvStore: distributedKV.KVStore | null = null;
private readonly KV_ID = 'video_position_db';
// 初始化分布式键值数据库
async init() {
const options: distributedKV.Options = {
createIfMissing: true,
kvStoreType: distributedKV.KVStoreType.SINGLEVERSION
};
this.kvStore = await distributedKV.createKVStore(this.KV_ID, options);
}
// 记录当前播放位置(每次播放进度变化时调用)
async savePosition(videoId: string, position: number, deviceId: string) {
if (!this.kvStore) await this.init();
const record = {
videoId,
position, // 秒为单位
deviceId, // 记录是哪台设备保存的
timestamp: Date.now()
};
await this.kvStore.put(`pos_${videoId}`, JSON.stringify(record));
console.info(`保存播放位置: ${videoId} -> ${position}s`);
}
// 获取续播位置(打开视频时调用)
async getResumePosition(videoId: string): Promise<number | null> {
if (!this.kvStore) await this.init();
const raw = await this.kvStore.get(`pos_${videoId}`);
if (!raw) return null;
const record = JSON.parse(raw);
// 只接受 24 小时内的记录(避免返回过时位置)
const maxAge = 24 * 60 * 60 * 1000;
if (Date.now() - record.timestamp > maxAge) return null;
return record.position;
}
// 清除记录(用户主动从头播放时调用)
async clearPosition(videoId: string) {
if (!this.kvStore) return;
await this.kvStore.delete(`pos_${videoId}`);
}
}
小结:三个文件的职责划分
设备发现找到"谁是我的小伙伴",流转服务负责"怎么把东西送过去",续播接力管理"到了新设备从哪接上"。三个模块各司其职,通过统一的 Session 数据结构串联起来。
五、深度技术原理
软总线:设备之间的"隐形高速公路"
好,理解了代码之后,我们来聊聊背后的原理。
想象一下:你的手机和平板之间,没有一根数据线,也没有云端服务器做中转,那它们是怎么"对话"的?
答案就是 HarmonyOS 的分布式软总线(SoftBus)。
打个比方。普通的设备互联就像这样:你从深圳寄一个包裹到广州,先把包裹送到北京的总仓库(北京、上海、深圳三个仓库共用一个大系统),总仓库再转发到广州。绕了一大圈,速度慢,还贵。
软总线不一样,它更像是在深圳和广州之间建了一条隐形直达隧道。两座城市之间直接拉一根光纤,不需要绕到北京,速度快了好几倍。这条"隧道"可以走 WiFi,可以走 USB,甚至未来可以走蓝牙——但对开发者来说,这些细节完全被屏蔽了,你只需要"发送"和"接收",就像读写本地文件一样简单。
超级终端:信任关系的建立
你可能会问:万一邻居的手机偷偷往我平板上发东西怎么办?
这就涉及到"超级终端"的信任体系了。HarmonyOS 的设备信任关系建立在华为账号上。只有满足以下两个条件的设备,才能互相发现和通信:
- 同一华为账号登录。手机和平板登录的是同一个华为账号,系统认定这两台设备属于同一个人。
- 同一局域网或近距离。设备需要在同一个 WiFi 网络下,或者通过 HarmonyOS 的近场感知能力建立连接。
只有同时满足这两个条件,设备才会出现在"可用设备列表"里,你的视频才不会发错地方。
续播的原理:状态是如何跨设备保持的
你可能还有个疑问:手机上的播放位置,平板是怎么知道的?两台设备又没有共享内存。
这就靠 Distributed KV(分布式键值数据库) 了。
你可以把它理解为一个"共享笔记本"。手机在播放视频时,每隔几秒就在这本笔记本上写一行:“《流浪地球》第 42 分钟 37 秒”;平板打开同一个 App 时,去这本笔记本上一查——哦,原来上次停在这里,那我就从第 42 分 37 秒开始播。
这本"共享笔记本"在本地有一份副本,数据变化时会自动同步到同账号的所有设备上,速度很快,而且不需要云端服务器中转,数据走的就是前面说的那条"隐形直达隧道"。所以即便你坐飞机没有网络,只要手机和平板之前同步过一次位置(最后一次有网的时候),续播依然可以工作。
设备发现的两阶段策略
前面提到 HarmonyOS 用"蓝牙发现 + WiFi 传输"的组合策略,这里展开说说:
第一阶段:发现。设备通过蓝牙低功耗(BLE)广播自己的存在,就像在喊"我在这里!我叫小明的小米平板!“。手机收到广播后,根据华为账号信息判断是不是"自己人”,如果是就记录下来。这个过程功耗极低,手机搜半天也不怎么费电。
第二阶段:传输。确认目标设备可信后,切换到 WiFi Direct 或者同一个局域网,建立高速连接。这时候传输速度就上来了,1GB 的视频文件,在千兆局域网下几十秒就能传完。
BLE 负责"认识你",WiFi 负责"给你搬东西",两个协议各司其职。这是通信领域很常见的"控制面 + 数据面"分离思想,HarmonyOS 只是把它用在了分布式设备场景。
分布式键值数据库的工作机制
Distributed KV 之所以能做到"不需要云端服务器",关键在于它的本地优先 + 增量同步机制。
每台设备的本地都有一份完整的数据副本,数据写入时先落本地数据库,然后通过 SoftBus 将增量变更(只同步变化的部分,而不是整个数据库)推送给同账号设备。推送使用基于版本的乐观锁——如果两台设备同时修改了同一份数据,版本号更高的那次修改优先,冲突概率极低,因为播放位置这类数据天然不存在并发写入。
为什么不用传统的数据库同步?想象一下手机在地铁里没有网络,此时平板也离线,两台设备都独立播放了同一个视频(各自记住了不同的位置)。网络恢复后,谁的位置是正确的?分布式 KV 用"最后写入胜出(Last Write Wins)"策略配合时间戳来解决这个问题,简单高效,适合大多数消费级场景。
流转会话的生命周期管理
一个完整的流转会话,从创建到销毁,经历了五个状态:
① 准备(Preparing):源设备找到目标设备,建立通信通道,协商传输参数。这个阶段用户感知到的是"正在发现设备"。
② 传输(Transferring):文件或状态数据开始搬运。对于小文件(几百 KB 以内),这个阶段几乎瞬间完成;对于大视频文件,则会显示传输进度条。
③ 确认(Confirming):文件到达目标设备后,目标 App 被唤起并验证数据完整性(比如 MD5 校验)。确认无误后进入完成状态。
④ 完成(Completed):流转成功,两台设备上的 App 均显示当前状态,Session 进入"已过期"倒计时(默认 30 分钟),防止资源泄漏。
⑤ 失败/取消(Failed/Cancelled):网络中断、目标设备拒绝接收、磁盘空间不足等情况都会触发失败状态。此时源设备会提示用户原因,目标设备不会启动任何 App。
理解这五个状态,对于调试流转问题非常重要——当你发现"流转卡住了",看一下 Session 日志里卡在哪一步,排查方向就清晰多了。
六、常见问题解答
Q1:跨设备流转支持所有 App 吗?
不是的。 跨设备流转需要在应用层主动接入 HarmonyOS 的分布式 SDK。目前主流的华为自带应用(华为视频、华为音乐、华为浏览器等)已经内置支持。如果你用的是第三方 App,目前还无法自动享受流转体验——这也是 HarmonyOS 正在推动生态建设的方向之一。
Q2:传输过程中设备断开网络会怎样?
分两种情况。如果传输的是大文件(视频、图片),SoftBus 传输中途断开,网络恢复后会自动续传,不需要从头开始。如果传输的是小状态数据(播放位置、书签),它们已经存储在分布式 KV 数据库中,断网不影响读取——因为数据在本地有缓存。
Q3:设备之间需要连到同一个 WiFi 吗?
不完全是。 同一华为账号的设备,即便不在同一个局域网内,也可以通过 HarmonyOS 的分布式能力进行数据同步(延迟稍高)。但如果要进行大文件的高速传输,还是建议在同一 WiFi 网络下,速度会快很多。当然,如果你用的是 HarmonyOS 3.0 及以上版本,手机和平板在近距离时还能通过 HarmonyOS 超级终端一碰连 建立直连通道,完全不需要 WiFi。
Q4:哪些设备支持跨设备流转?
理论上所有 HarmonyOS 2.0 及以上版本的设备都支持基础的设备发现能力。但完整的流转体验(如视频续播接力)需要设备同时满足:搭载 HarmonyOS 2.0+、登录同一华为账号、打开"跨设备协同"开关。目前手机、平板、PC、手表、智慧屏都支持,具体能力因设备性能有所差异。
Q5:我的隐私安全有保障吗?
有。HarmonyOS 的跨设备通信默认走端到端加密,只有同一账号的可信设备之间才能建立连接。数据不经过第三方服务器中转,传输路径完全在本地局域网或设备直连通道内。此外,用户可以在设置中随时查看和管理"可信设备列表",随时撤销某台设备的访问权限。
Q6:可以同时往多台设备发送内容吗?
支持,但体验上建议一次只流转到一台设备。想象一下你点了一下分享,结果手机、平板、电视、手表同时开始播放同一个视频——那画面太美我不敢看。技术上系统支持建立多个并发 Session,但从产品体验角度,我们推荐用户在弹出的设备选择列表中一次选一台,确认后再流转。
Q7:流转会消耗很多流量吗?
基本不会。 流转走的是局域网直连(WiFi Direct 或同一 WiFi 网络),不消耗移动数据流量。只有在跨网络的特殊场景(如手机使用移动数据、平板连接另一个 WiFi)下,数据才会走云端中转,此时才会消耗流量。但这类场景较少见,大多数流转发生在家里或办公室的同一网络下。
七、运行效果
设备发现与流转全流程

八、扩展方向
1. 从单设备流转到多设备协同
目前的流转是一对一:手机 → 平板。但未来可以扩展为"一对多"接力。比如你在手机上开始导航,走到停车场换成手表继续步行指引,上楼后手表又可以流转到电视展示地图全景。这就需要一个"任务协调器"来管理多设备的交接顺序。
这种多跳流转(Multi-hop Transfer)的实现逻辑并不复杂:每个流转节点保存"上游"和"下游"信息,形成一条链表。每当任务流转到下一台设备,上一台设备就把自己标记为"已完成"并指向新设备。如果用户在某台设备上主动返回上一级,系统就顺着链路反向追溯,找到当前活跃的节点并从那里继续。整个链路最多支持三跳(手机→平板→PC),再长的话用户体验反而会变得混乱。
2. 从视频续播到应用状态整体迁移
视频只是一个小口子。未来完全可以把"流转"扩展到整个应用状态——你正在手机上编辑文档,流转后平板直接打开同一份文档、光标位置一样、批注还在。再进一步,想象一下你在手机上打了半小时字,流转到 PC 后焦点直接跳到打字位置,这才是真正的"工作流不中断"。
3. AI 加持的智能流转决策
目前需要用户手动选择目标设备,但未来可以引入 AI 推理:比如检测到你带着手表离开了手机,自动建议把手表的导航流转过来;检测到你坐到了平板旁边,自动推荐把视频流转到平板大屏看。体验越来越无感,才是流转的终极形态。
4. 跨品牌设备的开放流转
目前跨设备流转依赖 HarmonyOS 的软总线能力,局限性在于只能在 HarmonyOS 生态内工作。随着生态开放协议(如 FIDO、ODCP)的成熟,未来或许能看到 HarmonyOS 设备与 Android、iOS 设备之间的有限流转——比如图片和文档的互传,而不需要云端中转。
一个更务实的中间路线是"云端兜底"模式:当 HarmonyOS 设备发现目标设备不是同生态时,自动降级到云同步(如华为云空间)。虽然速度不如软总线,但在跨品牌场景下提供了可用性保障。这样用户体验就有了一个兜底选项——流转能力不会因为设备生态不同而完全失效。
5. 跨 App 的通用流转协议
目前流转能力主要在单个 App 内部生效(华为视频续播接力只能在华为视频 App 内),但未来可以抽象出一套"跨 App 通用流转协议"。想象一下:你在微信里点开一个视频链接,看了几分钟想流转到平板,流转的不是视频 App 的状态,而是"这个 URL 链接"本身——平板收到后自动用浏览器打开并继续计时。
这套协议需要在系统层面定义"流转上下文"的通用格式:包含 URL、打开时间、停留时长、关联参数(如文章 ID)。任何 App 都可以声明"我支持接收流转上下文",系统负责路由和解析,App 只需要实现一个标准的回调接口即可。这种设计思路和 Android 的 App Link / iOS 的 Universal Link 有相似之处,但加上分布式状态同步后威力更大。
更多推荐

所有评论(0)