Skip to content

Commit 53b2465

Browse files
committed
📹 Make videos with SwiftUI
1 parent 9d29204 commit 53b2465

Some content is hidden

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

45 files changed

+4984
-2
lines changed

.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata

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

Examples/CLIExample/.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
.DS_Store
2+
/.build
3+
/Packages
4+
xcuserdata/
5+
DerivedData/
6+
.swiftpm/configuration/registries.json
7+
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
8+
.netrc

Examples/CLIExample/index.swift

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
import AppKit
2+
import ArgumentParser
3+
import Foundation
4+
import StreamUI
5+
import SwiftUI
6+
import VideoViews
7+
8+
// @Observable
9+
// class StreamUISettings {
10+
// var fps: Int32 = 30
11+
// var width: CGFloat = 1080
12+
// var height: CGFloat = 1920
13+
// var captureDuration: Int = 20
14+
// var saveVideoFile: Bool = true
15+
//
16+
// var livestreamSettings: [LivestreamSettings] = []
17+
// }
18+
//
19+
//// Define the command line arguments
20+
// struct StreamUICLIArgs: ParsableArguments {
21+
// @Option(help: "Frames per second")
22+
// var fps: Int
23+
//
24+
// @Option(help: "Width of the video")
25+
// var width: Int
26+
//
27+
// @Option(help: "Height of the video")
28+
// var height: Int
29+
//
30+
// @Option(help: "Capture duration in seconds")
31+
// var captureDuration: Int
32+
//
33+
// @Flag(help: "Save video file")
34+
// var saveVideoFile: Bool = false
35+
//
36+
// @Option(help: "RTMP connection URL")
37+
// var rtmpConnection: String?
38+
//
39+
// @Option(help: "Stream key")
40+
// var streamKey: String?
41+
// }
42+
//
43+
// extension StreamUICLIArgs {
44+
// func update(_ settings: StreamUISettings) {
45+
// settings.fps = Int32(fps)
46+
// settings.width = CGFloat(width)
47+
// settings.height = CGFloat(height)
48+
//
49+
// settings.captureDuration = captureDuration
50+
// settings.saveVideoFile = saveVideoFile
51+
//
52+
// if let rtmpConnection = rtmpConnection, let streamKey = streamKey {
53+
// let livestreamSettings = LivestreamSettings(
54+
// rtmpConnection: rtmpConnection,
55+
// streamKey: streamKey
56+
// )
57+
// settings.livestreamSettings.append(livestreamSettings)
58+
// }
59+
// }
60+
// }
61+
//
62+
// @main
63+
// struct CLIExample: App {
64+
// @Environment(\.displayScale) private var displayScale
65+
//
66+
// @State private var settings: StreamUISettings = {
67+
// let settings = StreamUISettings()
68+
// if CommandLine.argc > 1 {
69+
// do {
70+
// let args = try StreamUICLIArgs.parse()
71+
// args.update(settings)
72+
// } catch {
73+
// print("Error: Could not parse arguments")
74+
// print(CommandLine.arguments.dropFirst().joined(separator: " "))
75+
// print(StreamUICLIArgs.helpMessage())
76+
// exit(1) // Exit if argument parsing fails
77+
// }
78+
// } else {
79+
// settings.fps = 30
80+
// settings.width = 1080
81+
// settings.height = 1920
82+
// settings.captureDuration = 15
83+
// settings.saveVideoFile = true
84+
// settings.livestreamSettings = [
85+
// .init(rtmpConnection: "rtmp://localhost/live", streamKey: "streamKey")
86+
// ]
87+
// }
88+
// return settings
89+
// }()
90+
//
91+
// var body: some Scene {
92+
// WindowGroup {
93+
// StreamUI(
94+
// fps: settings.fps,
95+
// width: settings.width,
96+
// height: settings.height,
97+
// displayScale: displayScale,
98+
//// captureDuration: .seconds(settings.captureDuration),
99+
// saveVideoFile: settings.saveVideoFile,
100+
// livestreamSettings: settings.livestreamSettings
101+
// ) {
102+
// BasicCounterView(initialCounter: 0)
103+
//// SpriteKitTestView()
104+
//// SimpleWebView()
105+
//// VideoTestView()
106+
//// RandomSwiftUIComponentsTestView()
107+
// }
108+
// }
109+
// }
110+
// }
111+
112+
//
113+
//
114+
// Or if you don't want the View to see the rendering with controls you can:
115+
//
116+
//
117+
118+
@main
119+
enum CLIExample {
120+
static func main() async throws {
121+
print("huhu")
122+
let recorder = createStreamUIRecorder(
123+
fps: 30,
124+
width: 1080,
125+
height: 1920,
126+
displayScale: 2.0,
127+
captureDuration: .seconds(10),
128+
saveVideoFile: true
129+
) {
130+
// BasicCounterView(initialCounter: 0)
131+
// VideoTestView()
132+
// ImageTestView()
133+
SoundTestView()
134+
}
135+
136+
let controlledClock = recorder.controlledClock
137+
138+
recorder.startRecording()
139+
140+
// try await Task.sleep(for: .seconds(5))
141+
//// try await controlledClock.sleep(for: 5.0)
142+
// recorder.pauseRecording()
143+
// try await Task.sleep(for: .seconds(10))
144+
//// try await controlledClock.sleep(for: 10.0)
145+
// recorder.resumeRecording()
146+
// recorder.stopRecording()
147+
// try await Task.sleep(for: .seconds(2))
148+
// recorder.resumeRecording()
149+
150+
// Wait for the recording to complete
151+
await recorder.waitForRecordingCompletion()
152+
153+
// while recorder.isRecording {
154+
// print("while recording")
155+
// try await Task.sleep(for: .seconds(5))
156+
// print("waited five secs")
157+
// recorder.isPaused.toggle()
158+
// try await Task.sleep(for: .seconds(2))
159+
// recorder.isPaused.toggle()
160+
// }
161+
162+
// try await Task.sleep(for: .seconds(1.0))
163+
}
164+
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import AVFoundation
2+
import StreamUI
3+
import SwiftUI
4+
5+
public struct BasicCounterView: View {
6+
@Environment(\.recorder) private var recorder
7+
8+
@State private var counter: Int
9+
@State private var timer: Timer?
10+
11+
@State private var paused: Bool = false
12+
13+
let audioUrl = URL(string: "https://dl.espressif.com/dl/audio/ff-16b-2c-44100hz.m4a")!
14+
15+
private var timerTask: Task<Void, Never>?
16+
17+
public init(initialCounter: Int = 10) {
18+
_counter = State(initialValue: initialCounter)
19+
// remoteAudioUrl = URL(string: "https://dl.espressif.com/dl/audio/ff-16b-2c-44100hz.m4a")!
20+
}
21+
22+
public var body: some View {
23+
VStack {
24+
Text("Counter: \(counter)")
25+
.font(.largeTitle)
26+
.foregroundColor(.green)
27+
.padding()
28+
29+
Text("Counter: \(recorder?.controlledClock.elapsedTime)")
30+
.font(.largeTitle)
31+
.foregroundColor(.green)
32+
.padding()
33+
34+
if let frameCount = recorder?.frameTimer.frameCount {
35+
Text("Current Frame -> \(frameCount)")
36+
.font(.largeTitle)
37+
.foregroundColor(.orange)
38+
.foregroundColor(.green)
39+
.padding()
40+
} else {
41+
Text("No frame count")
42+
.font(.largeTitle)
43+
.foregroundColor(.orange)
44+
.foregroundColor(.green)
45+
.padding()
46+
}
47+
}
48+
// .onChange(of: recorder?.frameCount) { _ in
49+
//// print("new frame count", newCount)
50+
// }
51+
.onAppear {
52+
startTimer()
53+
// setupAudioPlayer()
54+
55+
Task {
56+
print("going to load")
57+
try await recorder?.loadAudio(from: audioUrl)
58+
print("loaded audio")
59+
60+
recorder?.playAudio(from: audioUrl)
61+
}
62+
// recorder.l
63+
// playAudio()
64+
}
65+
.onDisappear {
66+
print("[DEBUG] Stop Timer")
67+
stopTimer()
68+
}
69+
// .onChange(of: counter) { newCounter in
70+
// print("New counter", newCounter)
71+
// if newCounter == 5 {
72+
// print("pausing")
73+
// recorder?.isPaused.toggle()
74+
// }
75+
//
76+
// if newCounter == 10 {
77+
// print("resuming")
78+
// recorder?.isPaused.toggle()
79+
// }
80+
// }
81+
}
82+
83+
private func startTimer() {
84+
Task {
85+
while true {
86+
print("TIMER LOOP")
87+
// print("RECORDER COUNT", recorder?.frameCount)
88+
try await recorder?.controlledClock.clock.sleep(for: .seconds(1.0))
89+
print("TIMER LOOP AFETER")
90+
// try await recorder?.clock.sleep(for: .seconds(1.0 / Double(30)))
91+
counter += 1
92+
}
93+
}
94+
95+
// timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
96+
// counter += 1
97+
// }
98+
}
99+
100+
private func stopTimer() {
101+
// timerTask?.cancel()
102+
// timerTask = nil
103+
}
104+
105+
private func setupAudioPlayer() {
106+
// audioPlayer = AVPlayer(url: remoteAudioUrl)
107+
}
108+
109+
private func playAudio() {
110+
// audioPlayer?.play()
111+
}
112+
}
113+
114+
// #Preview {
115+
// BasicCounterView()
116+
// }
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
//
2+
// File.swift
3+
//
4+
//
5+
// Created by Jordan Howlett on 6/25/24.
6+
//
7+
8+
import StreamUI
9+
import SwiftUI
10+
11+
public struct ImageTestView: View {
12+
@Environment(\.recorder) private var recorder
13+
14+
let url = "https://sample-videos.com/img/Sample-jpg-image-5mb.jpg"
15+
16+
public init() {}
17+
public var body: some View {
18+
StreamingImage(url: URL(string: url)!)
19+
.frame(width: 500, height: 900)
20+
21+
// VideoPlayer(player: AVPlayer(url: URL(string: "https://file-examples.com/storage/fed5266c9966708dcaeaea6/2017/04/file_example_MP4_480_1_5MG.mp4")!))
22+
// .frame(height: 400)
23+
}
24+
}

0 commit comments

Comments
 (0)