为了保证应用程序能稳定运行,在业务流程设计之初,就应该考虑到异常流程处理。想象一下,你的应用运行在千家万户的设备上。千家万户的网络问题、DNS 问题、鉴权问题,各种问题源源不断地涌现出来。
倘若不处理异常流程,上线即是灾难。我们强烈建议你在设计业务逻辑之初就将异常流程考虑在内。Netless 互动白板提供了多种方式让你能处理实时房间的异常流程。
在加入房间时,我们应该设计并考虑「加入失败」的情况。通过如下代码,我们可以捕获 SDK 因加入房间失败而抛出的错误。根据报错内容,我们可以定位加入失败的原因,据此来调整业务代码。
JavaScript
whiteWebSdk.joinRoom({uuid, roomToken})
.then(function (room) {
// 加入房间成功,获取 room 对象
})
.catch(function(err) {
// 加入房间失败,捕获 err 对象
});
iOS - Objective-C
[whiteSdk joinRoomWithConfig:roomConfig
callbacks:callbacks
completionHandler:^(BOOL success, WhiteRoom * _Nonnull room, NSError * _Nonnull error) {
if (success) {
// 加入房间成功,获取 room 对象
} else {
// 加入房间失败,捕获 err 对象
}
}];
Android - Java
whiteSdk.joinRoom(new RoomParams(uuid, roomToken), callbacks, new Promise<Room>() {
@Override
public void then(Room room) {
// 加入房间成功,获取 room 对象
}
@Override
public void catchEx(SDKError err) {
// 加入房间失败,捕获 err 对象
}
});
connect failed: room "68e3f4b0f96e11eaa270b5c2c93f0f1c" not found
这个报错标明,uuid
为 68e3f4b0f96e11eaa270b5c2c93f0f1c
的房间未被创建过,或该房间已被删除。
invalid room token: undefined
加入房间需要 roomToken
字段,这个字段用于确定用户是否有权限加入房间,是必填的字段。
invalid room token: "NETLESSSDK_YWs9MzRsNzBoQ2dINFkxZ1BCYWpqbU5Ick9qM3FGeDFMWE5GY3R0Jm5vbmNlPTE2MDA0MDczOTY0NzIwMCZyb2xlPTAmc2lnPWM0OTAyYjcyNjE4Yzc0NDM5NTQxYjg1NGE5MTgwNjFiMThjYzUyOTNlZTVhODBjZmQyZDUwNjlhMWI3MTM2MTg"
加入房间填写的 roomToken
格式错误。遇到这个问题时,应该确认这个 roomToken
是否是用正确的方式生成的,以及它真的是 roomToken
,还是别的什么东西。
在这个例子中,我们发现它的前缀是
NETLESSSDK_
。显然它是一个sdkToken
,而不是roomToken
,一定是后端调用了错误的方法,从而生成了错误的roomToken
。
can't update to writable. you don't have permission
这个错误表明,你想以可写模式加入房间,但 roomToken
却只有只读权限。要避开这个错误,有两个方法。
让后端重新生成具有可写权限以上角色的 roomToken
。如 admin
、writer
级别的权限。
在客户端(或前端)加入房间时,添加参数 isWritable: false
。这样可以以只读模式加入房间。
connect failed: cannnot join room: expired token
这个错误表明,你的 roomToken
已过期。让后端重新生成一个新的 roomToken
给你即可。后端可以通过 lifespan
来设置 roomToken
的过期时间,请结合业务思考,多长的过期时间是恰当的。
room's active sessions out of limit (max users 50)
这个错误表明,该房间已满,无法再加入新用户。
connect failed: room "68e3f4b0f96e11eaa270b5c2c93f0f1c" is ban
这个房间被封禁。说明后端调用过 PATCH api.netless.link/v5/rooms/:uuid
方法。让后端解封之后才能加入房间。
实时房间可能因为各种原因断开连接,你需要在加入房间时注册回调函数,以监听实时房间因异常情况断开连接的事件。
JavaScript
whiteWebSdk.joinRoom({uuid, roomToken}, {
onDisconnectWithError: function(err) {
// 房间因为错误,和服务端断开连接
},
onKickedWithReason: function(err) {
// 用户被踢出房间
},
});
iOS - Objective-C
@protocol WhiteRoomCallbackDelegate <NSObject>
@optional
/** 用户被踢出房间 */
- (void)fireKickedWithReason:(NSString *)reason;
/** 房间因为错误,和服务端断开连接 */
- (void)fireDisconnectWithError:(NSString *)error;
@end
[whiteSdk joinRoomWithConfig: roomConfig
callbacks: callbaclDelegate // 实现 WhiteRoomCallbackDelegate 的对象
completionHandler: ^(BOOL success, WhiteRoom * _Nonnull room, NSError * _Nonnull error) {
// 加入成功或失败
}];
Android - Java
whiteSdk.joinRoom(new RoomParams(uuid, roomToken), new AbstractRoomCallbacks() {
@Override
public void onKickedWithReason(String reason) {
// 用户被踢出房间
}
@Override
public void onDisconnectWithError(Exception exception) {
// 房间因为错误,和服务端断开连接
}
}, new Promise<Room>() {
@Override
public void then(Room room) {
// 加入房间成功,获取 room 对象
}
@Override
public void catchEx(SDKError err) {
// 加入房间失败,捕获 err 对象
}
});
用户被踢出房间,往往不是因为用户自身(网络问题、权限)导致的,而是某些业务操作导致。我们可以在 onKickedWithReason
中获取 reason
字段来获知被踢的原因。该字段可能的情况如下。
Reason | 描述 |
---|---|
roomBan | 房间被封禁,所有人被迫离开 |
kickByAdmin | 被管理员踢出 |
replaceByOther | 被相同权限的用户异地登录,被迫下线 |
roomDelete | 房间被删除,所有人被迫离开 |
roomZombie | 房间被冻结,所有人被迫离开 |
crash | 房间崩溃,所有人被迫离开 |
因为网络或其他错误和服务器断开连接,这往往是因为用户自身原因导致的。我们可以在 onDisconnectWithError
中获取「错误对象」,根据其附带的描述信息定位具体问题。
实时房间如果遇到网络问题而断线,除非判定为发生了不可恢复的错误,否则,尝试重新建连。只有在多次重连失败之后,才会宣告断开连接,并回调 onDisconnectWithError
。
断线重连过程中,房间的 phase
会发生变化。
Connected
→ Reconnecting
→ Connected
Connected
→ Reconnecting
→ Disconnected
更多关于房间状态(phase
)变化的信息,请参考《实时房间状态管理》
在准备回放房间录像时,我们应该设计并考虑「回放失败」的情况。通过如下代码,我们可以捕获 SDK 因加入回放失败而抛出的错误。根据报错内容,我们可以定位回放失败的原因,据此来调整业务代码。
JavaScript
whiteWebSdk.replayRoom({room, roomToken})
.then(function (player) {
// 获取到回放数据,成功初始化播放器实例
}).catch(function(err) {
// 获取回放数据失败
});
iOS - Objective-C
[self.sdk createReplayerWithConfig: playerConfig
callbacks: callbacks
completionHandler:^(BOOL success, WhitePlayer * _Nonnull player, NSError * _Nonnull error) {
if (error) {
// 获取回放数据失败
} else {
// 获取到回放数据,成功初始化播放器实例
}
}];
Android - Java
whiteSdk.createPlayer(playerConfiguration, callbacks, new Promise<Player>() {
@Override
public void then(Player player) {
// 获取到回放数据,成功初始化播放器实例
}
@Override
public void catchEx(SDKError t) {
// 获取回放数据失败
}
});
couldn't find any matching slices
这个错误表明,你限定的播放范围内找不到可播放的片段。造成这个错误的原因有多种,你需要一一排查。
你的播放范围设置错误。例如,你想播放昨天上午 10:00 开始的一段录像,却误把时间设置成昨天下午 10:00。将范围设置正确即可避免这个错误,具体可以参考 《录制与回放》。
也许,录像还没来得及产生。Netless 录制实时房间,需要消耗一定时间才能生成录像。尽管这个过程很快,但是也不是一瞬间就能完成(从几秒钟到几分钟不等)。如果你迫切地想播放几秒钟前刚刚结束的房间录制,很可能得到这个错误。此时只需要等待几分钟,就可以播放了。
也许,这个范围本就不该有可播放片段。在业务上,你可能无法得知录像到底录到了那个时间段。你可能就想试试这个时间段有没有可播的内容,然后就得到了这个报错。那这个报错就是符合预期的行为。如果你仅仅想判断某个时间区间是否有录像可播,可以调用 isPlayable
方法来判断(该方法和平台相关,请参考各个平台的文档)。
mode of room "68e3f4b0f96e11eaa270b5c2c93f0f1c" expects "historied", instead of "persistent"
这个错误表明,你希望回放的房间没有开启录制功能,因此根本就不可能产生任何录像以供回放。注意,后端需要在创建房间时加入 isRecorad: true
字段,以确保录制功能是开启的。
slice "JOosYfmDEeqlpC37mo-13Q" not found
这个错误表明,你指定的录像片段不存在。你需要确认该片段 uuid
是否正确(在该例子中,uuid
为 JOosYfmDEeqlpC37mo-13Q
。
如果 uuid
是正确的,但依然报这个错,则可能是后端在创建房间时没有开启录制功能导致的。如果录制功能也开启了,则可能是因为录像片段尚未生成所导致的。Netless 录制实时房间,需要消耗一定时间才能生成录像。尽管这个过程很快,但是也不是一瞬间就能完成(从几秒钟到几分钟不等)。
回放可能因为各种原因而停止,一旦停止,该 player
实例将无法被调用任何方法。你需要在开始回放时注册回调函数,以监听回放因错误而停止的事件。
JavaScript
whiteWebSdk.replayRoom(replayRoomParams, {
onStoppedWithError: function(err) {
// 播放器因为错误而终止
},
});
iOS - Objective-C
@protocol WhitePlayerEventDelegate <NSObject>
@optional
// 播放器因为错误而终止
- (void)stoppedWithError:(NSError *)error;
@end
[self.sdk createReplayerWithConfig: playerConfig
callbacks: callbaclDelegate // 实现 WhitePlayerEventDelegate 的对象
completionHandler:^(BOOL success, WhitePlayer * _Nonnull player, NSError * _Nonnull error) {
// 回放成功或失败
}];
Android - Java
whiteSdk.createPlayer(playerConfiguration, new AbstractPlayerEventListener() {
@Override
public void onStoppedWithError(SDKError error) {
// 播放器因为错误而终止
}
}, new Promise<Player>() {
@Override
public void then(Player player) {
// 回放成功
}
@Override
public void catchEx(SDKError t) {
// 回放失败
}
});