Skip to content

Commit 3f059ad

Browse files
authored
Initial commit (#1)
1 parent 5f7a7d5 commit 3f059ad

File tree

131 files changed

+6867
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

131 files changed

+6867
-0
lines changed

.gitignore

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# Xcode
2+
#
3+
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4+
5+
## User settings
6+
xcuserdata/
7+
8+
## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
9+
*.xcscmblueprint
10+
*.xccheckout
11+
12+
## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
13+
build/
14+
DerivedData/
15+
*.moved-aside
16+
*.pbxuser
17+
!default.pbxuser
18+
*.mode1v3
19+
!default.mode1v3
20+
*.mode2v3
21+
!default.mode2v3
22+
*.perspectivev3
23+
!default.perspectivev3
24+
25+
## Obj-C/Swift specific
26+
*.hmap
27+
28+
## App packaging
29+
*.ipa
30+
*.dSYM.zip
31+
*.dSYM
32+
33+
## Playgrounds
34+
timeline.xctimeline
35+
playground.xcworkspace
36+
37+
# Swift Package Manager
38+
#
39+
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
40+
# Packages/
41+
# Package.pins
42+
# Package.resolved
43+
# *.xcodeproj
44+
#
45+
# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
46+
# hence it is not needed unless you have added a package configuration file to your project
47+
.swiftpm
48+
49+
.build/
50+
51+
# CocoaPods
52+
#
53+
# We recommend against adding the Pods directory to your .gitignore. However
54+
# you should judge for yourself, the pros and cons are mentioned at:
55+
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
56+
#
57+
# Pods/
58+
#
59+
# Add this line if you want to avoid checking in source code from the Xcode workspace
60+
# *.xcworkspace
61+
62+
# Carthage
63+
#
64+
# Add this line if you want to avoid checking in source code from Carthage dependencies.
65+
# Carthage/Checkouts
66+
67+
Carthage/Build/
68+
69+
# Accio dependency management
70+
Dependencies/
71+
.accio/
72+
73+
# fastlane
74+
#
75+
# It is recommended to not store the screenshots in the git repo.
76+
# Instead, use fastlane to re-generate the screenshots whenever they are needed.
77+
# For more information about the recommended setup visit:
78+
# https://docs.fastlane.tools/best-practices/source-control/#source-control
79+
80+
fastlane/report.xml
81+
fastlane/Preview.html
82+
fastlane/screenshots/**/*.png
83+
fastlane/test_output

Package.resolved

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// swift-tools-version: 5.7
2+
// The swift-tools-version declares the minimum version of Swift required to build this package.
3+
4+
import PackageDescription
5+
6+
let package = Package(
7+
name: "DolbyIORTSUIKit",
8+
defaultLocalization: "en",
9+
platforms: [.iOS(.v15)],
10+
products: [
11+
.library(
12+
name: "DolbyIOUIKit",
13+
targets: ["DolbyIOUIKit"]),
14+
.library(
15+
name: "DolbyIORTSCore",
16+
targets: ["DolbyIORTSCore"]),
17+
.library(
18+
name: "DolbyIORTSUIKit",
19+
targets: ["DolbyIORTSUIKit"])
20+
],
21+
dependencies: [
22+
.package(url: "https://github.com/millicast/millicast-sdk-swift-package", from: "1.5.0")
23+
],
24+
targets: [
25+
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
26+
// Targets can depend on other targets in this package, and on products in packages this package depends on.
27+
.target(
28+
name: "DolbyIOUIKit",
29+
dependencies: [],
30+
path: "Sources/DolbyIOUIKit",
31+
resources: [.process("Resources")]
32+
),
33+
.target(
34+
name: "DolbyIORTSCore",
35+
dependencies: [
36+
.product(name: "MillicastSDK", package: "millicast-sdk-swift-package")
37+
],
38+
path: "Sources/DolbyIORTSCore"
39+
),
40+
.target(
41+
name: "DolbyIORTSUIKit",
42+
dependencies: [
43+
"DolbyIOUIKit",
44+
"DolbyIORTSCore"
45+
],
46+
path: "Sources/DolbyIORTSUIKit",
47+
resources: [.process("Resources")]
48+
),
49+
.testTarget(
50+
name: "DolbyIOUIKitTests",
51+
dependencies: ["DolbyIOUIKit"]),
52+
.testTarget(
53+
name: "DolbyIORTSCoreTests",
54+
dependencies: ["DolbyIORTSCore"]),
55+
.testTarget(
56+
name: "DolbyIORTSUIKitTests",
57+
dependencies: ["DolbyIORTSUIKit", "DolbyIORTSCore"])
58+
]
59+
)
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
//
2+
// StreamSourceBuilder.swift
3+
//
4+
5+
import Foundation
6+
import MillicastSDK
7+
8+
final class StreamSourceBuilder {
9+
10+
enum BuildError: Error {
11+
case missingVideoTrack
12+
case missingAudioTrack
13+
}
14+
15+
struct TrackItem {
16+
let trackType: StreamSource.TrackType
17+
let mediaType: StreamSource.MediaType
18+
19+
/// Initialises the Track Item, if possible from the passed-in String
20+
/// - Parameter track: A string value passed in by the SDK and is expected to be of format `{mediaType}/{trackType}`
21+
init?(track: String) {
22+
let trackInfoList = track.split(separator: "/")
23+
24+
guard
25+
trackInfoList.count == 2,
26+
let mediaType = StreamSource.MediaType(rawValue: String(trackInfoList[0])),
27+
let trackType = StreamSource.TrackType(rawValue: String(trackInfoList[1]))
28+
else {
29+
return nil
30+
}
31+
self.mediaType = mediaType
32+
self.trackType = trackType
33+
}
34+
}
35+
36+
let identifier: UUID
37+
private(set) var streamId: String
38+
private(set) var sourceId: StreamSource.SourceId
39+
private(set) var supportedTrackItems: [TrackItem]
40+
private(set) var videoTrack: StreamSource.VideoTrackInfo?
41+
private(set) var audioTracks: [StreamSource.AudioTrackInfo] = []
42+
private(set) var availableVideoQualityList: [StreamSource.VideoQuality] = [.auto]
43+
private(set) var preferredVideoQuality: StreamSource.VideoQuality = .auto
44+
private(set) var isPlayingAudio = false
45+
private(set) var isPlayingVideo = false
46+
47+
init(streamId: String, sourceId: String?, tracks: [String]) {
48+
identifier = UUID()
49+
self.streamId = streamId
50+
self.sourceId = StreamSource.SourceId(id: sourceId)
51+
52+
supportedTrackItems = tracks
53+
.compactMap { TrackItem(track: $0) }
54+
}
55+
56+
func addAudioTrack(_ track: MCAudioTrack, mid: String) {
57+
let trackItems = supportedTrackItems.filter({ $0.mediaType == .audio })
58+
guard
59+
!trackItems.isEmpty,
60+
audioTracks.count < trackItems.count
61+
else {
62+
return
63+
}
64+
let trackItem = trackItems[0]
65+
audioTracks.append(
66+
StreamSource.AudioTrackInfo(
67+
mid: mid,
68+
trackType: trackItem.trackType,
69+
mediaType: trackItem.mediaType,
70+
track: track)
71+
)
72+
}
73+
74+
func addVideoTrack(_ track: MCVideoTrack, mid: String) {
75+
guard let trackItem = supportedTrackItems.first(where: { $0.mediaType == .video }) else {
76+
return
77+
}
78+
79+
videoTrack = StreamSource.VideoTrackInfo(
80+
mid: mid,
81+
trackType: trackItem.trackType,
82+
mediaType: trackItem.mediaType,
83+
track: track
84+
)
85+
}
86+
87+
func updatePreferredVideoQuality(_ videoQuality: StreamSource.VideoQuality) {
88+
guard availableVideoQualityList.contains(where: { $0 == videoQuality }) else {
89+
preferredVideoQuality = .auto
90+
return
91+
}
92+
93+
preferredVideoQuality = videoQuality
94+
}
95+
96+
func setAvailableVideoQualityList(_ list: [StreamSource.VideoQuality]) {
97+
availableVideoQualityList = list
98+
}
99+
100+
func setPlayingAudio(_ enable: Bool) {
101+
isPlayingAudio = enable
102+
}
103+
104+
func setPlayingVideo(_ enable: Bool) {
105+
isPlayingVideo = enable
106+
}
107+
108+
func build() throws -> StreamSource {
109+
guard !hasMissingAudioTrack else {
110+
throw BuildError.missingAudioTrack
111+
}
112+
113+
guard !hasMissingVideoTrack else {
114+
throw BuildError.missingVideoTrack
115+
}
116+
117+
return StreamSource(
118+
id: identifier,
119+
streamId: streamId,
120+
sourceId: sourceId,
121+
availableVideoQualityList: availableVideoQualityList,
122+
preferredVideoQuality: preferredVideoQuality,
123+
isPlayingAudio: isPlayingAudio,
124+
isPlayingVideo: isPlayingVideo,
125+
audioTracks: audioTracks,
126+
videoTrack: videoTrack
127+
)
128+
}
129+
}
130+
131+
// MARK: Helper functions
132+
// Note: Currently the Millicast SDK callbacks do not give us a way to associate the track we receive in onAudioTrack(..) and onVideoTrack(..) callbacks to a particular SourceId
133+
// The current implementation worked around it be checking for the missing `audio or video tracks` by comparing it with the `supportedTrackItems` - this SDK limitation leads
134+
// us in making assumptions that the mediaType will either be "audio" or "video".
135+
// TODO: Refactor this implementation when the SDK Callback API's are refined.
136+
137+
extension StreamSourceBuilder {
138+
var hasMissingAudioTrack: Bool {
139+
let audioTrackItems = supportedTrackItems.filter { $0.mediaType == .audio }
140+
return audioTracks.count < audioTrackItems.count
141+
}
142+
143+
var hasMissingVideoTrack: Bool {
144+
let hasVideoTrack = supportedTrackItems.contains { $0.mediaType == .video }
145+
return hasVideoTrack && videoTrack == nil
146+
}
147+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
//
2+
// RendererRegistry.swift
3+
//
4+
5+
import Foundation
6+
import MillicastSDK
7+
import os
8+
9+
protocol RendererRegistryProtocol: AnyObject {
10+
func registerRenderer(_ renderer: StreamSourceViewRenderer, for track: MCVideoTrack) async
11+
func deregisterRenderer(_ renderer: StreamSourceViewRenderer, for track: MCVideoTrack) async
12+
13+
func hasActiveRenderer(for track: MCVideoTrack) async -> Bool
14+
15+
func reset() async
16+
}
17+
18+
final actor RendererRegistry: RendererRegistryProtocol {
19+
private var rendererDictionary: [String: NSHashTable<StreamSourceViewRenderer>] = [:]
20+
21+
func registerRenderer(_ renderer: StreamSourceViewRenderer, for track: MCVideoTrack) {
22+
guard let trackID = track.getId() else {
23+
fatalError("Expect to have dictionary entry for the track before querying for renderer")
24+
}
25+
26+
if let renderers = rendererDictionary[trackID] {
27+
guard !renderers.contains(renderer) else {
28+
return
29+
}
30+
renderers.add(renderer)
31+
} else {
32+
let renderers = NSHashTable<StreamSourceViewRenderer>(options: .weakMemory)
33+
renderers.add(renderer)
34+
rendererDictionary[trackID] = renderers
35+
}
36+
}
37+
38+
func deregisterRenderer(_ renderer: StreamSourceViewRenderer, for track: MCVideoTrack) {
39+
guard let trackID = track.getId() else {
40+
fatalError("Expect to have dictionary entry for the track before querying for renderer")
41+
}
42+
43+
if let renderers = rendererDictionary[trackID] {
44+
renderers.remove(renderer)
45+
}
46+
}
47+
48+
func hasActiveRenderer(for track: MCVideoTrack) -> Bool {
49+
guard
50+
let trackID = track.getId(),
51+
let renderers = rendererDictionary[trackID]
52+
else {
53+
return false
54+
}
55+
56+
return renderers.count != 0
57+
}
58+
59+
func reset() {
60+
rendererDictionary.removeAll()
61+
}
62+
}

0 commit comments

Comments
 (0)