mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-10-28 00:33:06 +08:00
[feat] add mobile codec status and scrolling codec names
This commit is contained in:
@@ -136,7 +136,10 @@ typedef struct {
|
||||
didReceiveFileAtURL:(NSURL *)fileURL;
|
||||
- (void)rtcBridge:(CrossDeskRTCBridge *)bridge
|
||||
didUpdateNetworkStats:(CrossDeskNetworkStats)stats
|
||||
NS_SWIFT_NAME(rtcBridge(_:didUpdateNetworkStats:));
|
||||
localDecoder:(NSString *)decoder
|
||||
NS_SWIFT_NAME(rtcBridge(_:didUpdateNetworkStats:localDecoder:));
|
||||
- (void)rtcBridge:(CrossDeskRTCBridge *)bridge
|
||||
didUpdateRemoteVideoEncoder:(NSString *)encoder;
|
||||
@end
|
||||
|
||||
/// Objective-C++ boundary around the native MiniRTC C API.
|
||||
|
||||
@@ -1434,6 +1434,7 @@ Params MakeParams(const RTCState &state, const std::string &user_id,
|
||||
action.type = ControlType::host_infomation;
|
||||
action.i.platform = crossdesk::HostPlatform::IOS;
|
||||
action.i.supports_privacy_screen = true;
|
||||
action.i.supports_video_codec_status = true;
|
||||
CopyCString(action.i.host_name, sizeof(action.i.host_name),
|
||||
controller_name);
|
||||
action.i.host_name_size = std::strlen(action.i.host_name);
|
||||
@@ -1719,6 +1720,16 @@ Params MakeParams(const RTCState &state, const std::string &user_id,
|
||||
if (source == kControlStream) {
|
||||
RemoteAction action{};
|
||||
if (!action.from_json(std::string(data, size))) return;
|
||||
if (action.type == ControlType::video_codec_status) {
|
||||
NSString *encoder = [NSString stringWithUTF8String:action.vcs.encoder];
|
||||
DispatchMain(^{
|
||||
if (![self isControllerGenerationActive:generation]) return;
|
||||
id<CrossDeskRTCBridgeDelegate> delegate = self.delegate;
|
||||
if ([delegate respondsToSelector:@selector(rtcBridge:didUpdateRemoteVideoEncoder:)])
|
||||
[delegate rtcBridge:self didUpdateRemoteVideoEncoder:encoder ?: @""];
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (action.type == ControlType::privacy_status) {
|
||||
const auto message = action.to_json();
|
||||
NSData *status = [NSData dataWithBytes:message.data() length:message.size()];
|
||||
@@ -2034,13 +2045,17 @@ Params MakeParams(const RTCState &state, const std::string &user_id,
|
||||
mode == TraversalMode::P2P ? CrossDeskTraversalModeDirect
|
||||
: mode == TraversalMode::Relay ? CrossDeskTraversalModeRelay
|
||||
: CrossDeskTraversalModeUnknown};
|
||||
NSString *decoder = [[NSString alloc]
|
||||
initWithBytes:stats->video_decoder
|
||||
length:strnlen(stats->video_decoder, sizeof(stats->video_decoder))
|
||||
encoding:NSUTF8StringEncoding];
|
||||
DispatchMain(^{
|
||||
// Disconnect/reconnect can happen while this report waits on the UI queue.
|
||||
if (![self isControllerGenerationActive:generation]) return;
|
||||
id<CrossDeskRTCBridgeDelegate> delegate = self.delegate;
|
||||
if ([delegate respondsToSelector:
|
||||
@selector(rtcBridge:didUpdateNetworkStats:)]) {
|
||||
[delegate rtcBridge:self didUpdateNetworkStats:snapshot];
|
||||
@selector(rtcBridge:didUpdateNetworkStats:localDecoder:)]) {
|
||||
[delegate rtcBridge:self didUpdateNetworkStats:snapshot localDecoder:decoder ?: @""];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ struct RemoteNetworkReport {
|
||||
var mode: Mode = .unknown
|
||||
var srtpActive = false
|
||||
var rttMilliseconds: Double = -1
|
||||
var localVideoDecoder = ""
|
||||
}
|
||||
|
||||
struct RemoteNetworkSnapshot {
|
||||
@@ -23,6 +24,8 @@ struct RemoteNetworkSnapshot {
|
||||
var framesPerSecond = 0
|
||||
var videoLatencyMilliseconds: Double?
|
||||
var rttMilliseconds: Double?
|
||||
var remoteVideoEncoder = ""
|
||||
var localVideoDecoder = ""
|
||||
|
||||
var connectionMode: String {
|
||||
switch report?.mode {
|
||||
@@ -66,6 +69,11 @@ struct RemoteNetworkStatistics {
|
||||
private var frames: [(time: TimeInterval, latency: Double?)] = []
|
||||
private var displayedSnapshot = RemoteNetworkSnapshot()
|
||||
private var displayTime: TimeInterval?
|
||||
private var remoteVideoEncoder = ""
|
||||
|
||||
mutating func receiveRemoteVideoEncoder(_ encoder: String) {
|
||||
remoteVideoEncoder = encoder
|
||||
}
|
||||
|
||||
mutating func receive(_ report: RemoteNetworkReport, at now: TimeInterval) {
|
||||
self.report = report
|
||||
@@ -117,7 +125,9 @@ struct RemoteNetworkStatistics {
|
||||
report: now - reportTime < 3 ? report : nil,
|
||||
framesPerSecond: recentFrames.count,
|
||||
videoLatencyMilliseconds: delays.isEmpty ? nil : delays.reduce(0, +) / Double(delays.count),
|
||||
rttMilliseconds: now - rttTime < 3 ? averageRTT : nil
|
||||
rttMilliseconds: now - rttTime < 3 ? averageRTT : nil,
|
||||
remoteVideoEncoder: remoteVideoEncoder,
|
||||
localVideoDecoder: report?.localVideoDecoder ?? ""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1473,7 +1473,14 @@ final class RemoteSessionModel: NSObject, ObservableObject, CrossDeskRTCBridgeDe
|
||||
}
|
||||
|
||||
func rtcBridge(_ bridge: CrossDeskRTCBridge,
|
||||
didUpdateNetworkStats stats: CrossDeskNetworkStats) {
|
||||
didUpdateRemoteVideoEncoder encoder: String) {
|
||||
guard hasNetworkConsent, isConnected else { return }
|
||||
networkStatistics.receiveRemoteVideoEncoder(encoder)
|
||||
}
|
||||
|
||||
func rtcBridge(_ bridge: CrossDeskRTCBridge,
|
||||
didUpdateNetworkStats stats: CrossDeskNetworkStats,
|
||||
localDecoder: String) {
|
||||
guard hasNetworkConsent, isConnected else { return }
|
||||
func traffic(_ value: CrossDeskTrafficStats) -> RemoteTrafficStatistics {
|
||||
RemoteTrafficStatistics(inboundBitrate: value.inboundBitrate,
|
||||
@@ -1489,7 +1496,8 @@ final class RemoteSessionModel: NSObject, ObservableObject, CrossDeskRTCBridgeDe
|
||||
networkStatistics.receive(RemoteNetworkReport(
|
||||
video: traffic(stats.video), audio: traffic(stats.audio),
|
||||
data: traffic(stats.data), total: traffic(stats.total), mode: mode,
|
||||
srtpActive: stats.srtpActive.boolValue, rttMilliseconds: stats.rttMilliseconds
|
||||
srtpActive: stats.srtpActive.boolValue, rttMilliseconds: stats.rttMilliseconds,
|
||||
localVideoDecoder: localDecoder
|
||||
), at: ProcessInfo.processInfo.systemUptime)
|
||||
}
|
||||
|
||||
|
||||
@@ -1327,6 +1327,47 @@ private struct FloatingSessionMenu: View {
|
||||
}
|
||||
}
|
||||
|
||||
private struct ScrollingCodecName: View {
|
||||
let value: String
|
||||
@State private var textWidth: CGFloat = 0
|
||||
@State private var startedAt = Date()
|
||||
@ScaledMetric(relativeTo: .caption) private var lineHeight = 16.0
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { bounds in
|
||||
TimelineView(.animation(minimumInterval: 1.0 / 30, paused: textWidth <= bounds.size.width)) { context in
|
||||
Text(value)
|
||||
.lineLimit(1)
|
||||
.fixedSize()
|
||||
.background {
|
||||
GeometryReader { text in
|
||||
Color.clear
|
||||
.onAppear { textWidth = text.size.width }
|
||||
.onChange(of: text.size.width) { textWidth = $0; startedAt = Date() }
|
||||
}
|
||||
}
|
||||
.offset(x: offset(in: bounds.size.width, at: context.date))
|
||||
.frame(width: bounds.size.width, height: bounds.size.height, alignment: .leading)
|
||||
.clipped()
|
||||
}
|
||||
.onAppear { startedAt = Date() }
|
||||
.onChange(of: value) { _ in startedAt = Date() }
|
||||
.onChange(of: bounds.size.width) { _ in startedAt = Date() }
|
||||
}
|
||||
.frame(height: lineHeight)
|
||||
.accessibilityElement(children: .ignore)
|
||||
.accessibilityLabel(value)
|
||||
}
|
||||
|
||||
private func offset(in width: CGFloat, at time: Date) -> CGFloat {
|
||||
let distance = max(0, textWidth - width)
|
||||
guard distance > 0 else { return width - textWidth }
|
||||
// Pause for one second at each end, then repeat at 30 points/second.
|
||||
let phase = max(0, time.timeIntervalSince(startedAt)).truncatingRemainder(dividingBy: Double(distance) / 30 + 2)
|
||||
return -min(distance, CGFloat(max(0, phase - 1) * 30))
|
||||
}
|
||||
}
|
||||
|
||||
private struct FloatingNetworkStatistics: View {
|
||||
let snapshot: RemoteNetworkSnapshot
|
||||
let frameSize: CGSize
|
||||
@@ -1366,6 +1407,8 @@ private struct FloatingNetworkStatistics: View {
|
||||
}
|
||||
}
|
||||
.accessibilityElement(children: .combine)
|
||||
codec("被控端编码器", value: snapshot.remoteVideoEncoder)
|
||||
codec("控制端解码器", value: snapshot.localVideoDecoder)
|
||||
}
|
||||
.font(.caption.monospacedDigit())
|
||||
}
|
||||
@@ -1386,6 +1429,14 @@ private struct FloatingNetworkStatistics: View {
|
||||
.accessibilityElement(children: .combine)
|
||||
}
|
||||
|
||||
private func codec(_ title: String, value: String) -> some View {
|
||||
HStack(spacing: 8) {
|
||||
Text(title).foregroundStyle(.secondary).fixedSize()
|
||||
ScrollingCodecName(value: value.isEmpty ? "—" : value)
|
||||
}
|
||||
.accessibilityElement(children: .combine)
|
||||
}
|
||||
|
||||
private func trafficRow(_ title: String, statistics: RemoteTrafficStatistics?) -> some View {
|
||||
trafficRow(title,
|
||||
inbound: RemoteNetworkSnapshot.bitrate(statistics?.inboundBitrate),
|
||||
|
||||
Reference in New Issue
Block a user