Skip to content

Commit fd91c69

Browse files
Updated the documentation tests for livestreaming (#768)
1 parent a0730f6 commit fd91c69

File tree

2 files changed

+145
-0
lines changed

2 files changed

+145
-0
lines changed

DocumentationTests/DocumentationTests/DocumentationTests.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@
8181
40FFDCA22B640741004DA7A2 /* 14-livestream-player.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40FFDCA12B640741004DA7A2 /* 14-livestream-player.swift */; };
8282
40FFDCA42B640772004DA7A2 /* 15-long-press-to-focus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40FFDCA32B640772004DA7A2 /* 15-long-press-to-focus.swift */; };
8383
40FFDCA62B6408DE004DA7A2 /* 00-ringing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40FFDCA52B6408DE004DA7A2 /* 00-ringing.swift */; };
84+
845494D72DB9039000211413 /* 13-livestreaming.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845494D62DB9038900211413 /* 13-livestreaming.swift */; };
8485
84BA15AE2CA2EF420018DC51 /* 07-querying-call-members.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84BA15AD2CA2EF420018DC51 /* 07-querying-call-members.swift */; };
8586
84BA15B02CA2F04F0018DC51 /* 10-custom-events.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84BA15AF2CA2F04F0018DC51 /* 10-custom-events.swift */; };
8687
/* End PBXBuildFile section */
@@ -157,6 +158,7 @@
157158
40FFDCA12B640741004DA7A2 /* 14-livestream-player.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "14-livestream-player.swift"; sourceTree = "<group>"; };
158159
40FFDCA32B640772004DA7A2 /* 15-long-press-to-focus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "15-long-press-to-focus.swift"; sourceTree = "<group>"; };
159160
40FFDCA52B6408DE004DA7A2 /* 00-ringing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "00-ringing.swift"; sourceTree = "<group>"; };
161+
845494D62DB9038900211413 /* 13-livestreaming.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "13-livestreaming.swift"; sourceTree = "<group>"; };
160162
84BA15AD2CA2EF420018DC51 /* 07-querying-call-members.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "07-querying-call-members.swift"; sourceTree = "<group>"; };
161163
84BA15AF2CA2F04F0018DC51 /* 10-custom-events.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "10-custom-events.swift"; sourceTree = "<group>"; };
162164
/* End PBXFileReference section */
@@ -234,6 +236,7 @@
234236
84BA15AF2CA2F04F0018DC51 /* 10-custom-events.swift */,
235237
40FFDC3C2B63E6EC004DA7A2 /* 11-call-lifecycle.swift */,
236238
40FFDC3E2B63E734004DA7A2 /* 12-call-state.swift */,
239+
845494D62DB9038900211413 /* 13-livestreaming.swift */,
237240
40FFDC432B63E95D004DA7A2 /* 14-swiftui-vs-uikit.swift */,
238241
40FFDC452B63EA54004DA7A2 /* 15-migration-from-dolby.swift */,
239242
40F290AA2BDFB37000DCF136 /* 16-noise-cancellation.swift */,
@@ -468,6 +471,7 @@
468471
40FFDC512B63EF58004DA7A2 /* 05-call-controls.swift in Sources */,
469472
400D91D32B63DFA500EBA47D /* 06-querying-calls.swift in Sources */,
470473
401C1EF42D494CED00304609 /* 24-closed-captions.swift in Sources */,
474+
845494D72DB9039000211413 /* 13-livestreaming.swift in Sources */,
471475
40FFDC922B63FF70004DA7A2 /* 06-lobby-preview.swift in Sources */,
472476
4068C1252B67C056006B0BEE /* 03-callkit-integration.swift in Sources */,
473477
40FFDC762B63F7D6004DA7A2 /* ChatGloballyUsedVariables.swift in Sources */,
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
import StreamVideo
2+
import StreamVideoSwiftUI
3+
import StreamVideoUIKit
4+
import SwiftUI
5+
import Combine
6+
7+
@MainActor
8+
fileprivate func content() {
9+
container {
10+
struct LivestreamApp: App {
11+
@State var streamVideo: StreamVideo
12+
@State var call: Call
13+
14+
init() {
15+
let streamVideo = StreamVideo(
16+
apiKey: apiKey,
17+
user: .init(id: "martin"),
18+
token: .empty
19+
)
20+
let call = streamVideo.call(callType: "livestream", callId: "123")
21+
self.call = call
22+
self.streamVideo = streamVideo
23+
}
24+
25+
var body: some Scene {
26+
WindowGroup {
27+
LivestreamView(state: call.state)
28+
}
29+
}
30+
}
31+
32+
struct LivestreamView: View {
33+
34+
@StateObject var state: CallState
35+
36+
var body: some View {
37+
VStack {
38+
if state.backstage {
39+
backstageView
40+
} else if state.endedAt != nil {
41+
callEndedView
42+
} else {
43+
livestreamInfoView
44+
videoRendererView
45+
}
46+
}
47+
}
48+
49+
@ViewBuilder
50+
var backstageView: some View {
51+
if let startedAt = state.startsAt {
52+
Text("Livestream starting at \(startedAt.formatted())")
53+
} else {
54+
Text("Livestream starting soon")
55+
}
56+
if let session = state.session {
57+
let waitingCount = session.participants.filter({ $0.role != "host" }).count
58+
if waitingCount > 0 {
59+
Text("\(waitingCount) participants waiting")
60+
.font(.headline)
61+
.padding(.horizontal)
62+
}
63+
}
64+
}
65+
66+
@State var recordings: [CallRecording]?
67+
68+
@ViewBuilder
69+
var callEndedView: some View {
70+
Text("Call ended")
71+
.onAppear {
72+
if recordings == nil {
73+
Task {
74+
do {
75+
recordings = try await call.listRecordings()
76+
} catch {
77+
print("Error fetching recordings: \(error)")
78+
recordings = []
79+
}
80+
}
81+
}
82+
}
83+
84+
if let recordings, recordings.count > 0 {
85+
Text("Watch recordings:")
86+
ForEach(recordings, id: \.self) { recording in
87+
Button {
88+
if let url = URL(string: recording.url), UIApplication.shared.canOpenURL(url) {
89+
UIApplication.shared.open(url)
90+
}
91+
} label: {
92+
Text(recording.url)
93+
}
94+
}
95+
}
96+
}
97+
98+
@Injected(\.formatters.mediaDuration) private var formatter: MediaDurationFormatter
99+
100+
@ViewBuilder
101+
var livestreamInfoView: some View {
102+
HStack {
103+
if let duration = formatter.format(state.duration) {
104+
Text("Live for \(duration)")
105+
.font(.headline)
106+
.padding(.horizontal)
107+
}
108+
109+
Spacer()
110+
111+
Text("Live \(state.participantCount)")
112+
.bold()
113+
.padding(.all, 4)
114+
.foregroundColor(.white)
115+
.background(Color.blue)
116+
.cornerRadius(8)
117+
.opacity(state.backstage ? 0 : 1)
118+
.padding(.horizontal)
119+
}
120+
}
121+
122+
@ViewBuilder
123+
var videoRendererView: some View {
124+
GeometryReader { reader in
125+
if let first = state.participants.first(where: { hostIds.contains($0.userId) }) {
126+
VideoRendererView(id: first.id, size: reader.size) { renderer in
127+
renderer.handleViewRendering(for: first) { size, participant in }
128+
}
129+
} else {
130+
Text("The host's video is not available")
131+
}
132+
}
133+
.padding()
134+
}
135+
136+
var hostIds: [String] {
137+
state.members.filter { $0.role == "host" }.map(\.id)
138+
}
139+
}
140+
}
141+
}

0 commit comments

Comments
 (0)