iOS demo for the p2p-webrtc video-transform example

This commit is contained in:
Filipi Fuchter
2025-04-04 16:40:52 -03:00
parent 6466573b84
commit a1578bd67a
33 changed files with 1887 additions and 0 deletions

View File

@@ -0,0 +1,42 @@
import SwiftUI
import PipecatClientIOS
import PipecatClientIOSSmallWebrtc
struct CameraButtonView: View {
var trackId: MediaTrackId?
var isMuted: Bool
var body: some View {
GeometryReader { geometry in
let width = geometry.size.width
let circleSize = width * 0.9
let innerCircleSize = width * 0.82
ZStack {
Circle()
.stroke(Color.gray, lineWidth: 1)
.frame(width: circleSize)
if (!isMuted){
SmallWebRTCVideoViewSwiftUI(videoTrack: trackId, videoScaleMode: .fill)
.aspectRatio(1, contentMode: .fit)
.clipShape(Circle())
} else {
Circle()
.fill(Color.disabledVision)
.frame(width: innerCircleSize)
Image("vision")
.resizable()
.scaledToFit()
.frame(width: width * 0.3)
.foregroundColor(.green)
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity) // Ensures the ZStack is centered
}
}
}
#Preview {
CameraButtonView(trackId: nil, isMuted: true)
}

View File

@@ -0,0 +1,96 @@
import SwiftUI
struct ChatView: View {
@EnvironmentObject private var model: CallContainerModel
@State private var timer = Timer.publish(every: 0.5, on: .main, in: .common).autoconnect()
var body: some View {
VStack {
ScrollViewReader { scrollViewProxy in
ScrollView {
VStack(spacing: 10) {
ForEach(self.model.messages) { message in
MessageView(message: message)
.frame(maxWidth: .infinity, alignment: messageAlignment(for: message.type))
.padding(.horizontal)
.id(message.id)
}
}
.onChange(of: self.model.messages) { _, _ in
scrollToLastMessage(scrollViewProxy)
}
}
.onReceive(timer) { _ in
scrollToLastMessage(scrollViewProxy)
}
.onAppear {
scrollToLastMessage(scrollViewProxy)
}
}
}
.edgesIgnoringSafeArea(.bottom)
}
private func messageAlignment(for type: MessageType) -> Alignment {
switch type {
case .bot: return .leading
case .user: return .trailing
case .system: return .center
}
}
private func scrollToLastMessage(_ scrollViewProxy: ScrollViewProxy) {
if let lastMessageId = self.model.messages.last?.id {
withAnimation {
scrollViewProxy.scrollTo(lastMessageId, anchor: .bottom)
}
}
}
}
struct MessageView: View {
@ObservedObject var message: LiveMessage
var body: some View {
HStack {
if message.type == .bot {
Image(systemName: "gearshape")
.resizable()
.frame(width: 24, height: 24)
}
Text(message.content)
.padding(message.type == .system ? 5 : 10)
.foregroundColor(.white)
.background(messageBackgroundColor(for: message.type))
.cornerRadius(15)
.overlay(
RoundedRectangle(cornerRadius: 15)
.stroke(Color.gray.opacity(0.5), lineWidth: 1)
)
}
.padding(messagePadding(for: message.type))
}
private func messageBackgroundColor(for type: MessageType) -> Color {
switch type {
case .bot: return .black
case .user: return .gray
case .system: return .blue.opacity(0.6)
}
}
private func messagePadding(for type: MessageType) -> EdgeInsets {
switch type {
case .bot: return EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 40)
case .user: return EdgeInsets(top: 0, leading: 40, bottom: 0, trailing: 0)
case .system: return EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)
}
}
}
#Preview {
let mockModel = MockCallContainerModel()
let result = ChatView().environmentObject(mockModel as CallContainerModel)
return result
}

View File

@@ -0,0 +1,44 @@
import SwiftUI
struct MicrophoneView: View {
var audioLevel: Float // Current audio level
var isMuted: Bool // Muted state
var body: some View {
GeometryReader { geometry in
let width = geometry.size.width
let circleSize = width * 0.9
let innerCircleSize = width * 0.82
let audioCircleSize = CGFloat(audioLevel) * (width * 0.95)
ZStack {
Circle()
.stroke(Color.gray, lineWidth: 1)
.frame(width: circleSize)
Circle()
.fill(isMuted ? Color.disabledMic : Color.backgroundCircle)
.frame(width: innerCircleSize)
if !isMuted {
Circle()
.fill(Color.micVolume)
.opacity(0.5)
.frame(width: audioCircleSize)
.animation(.easeInOut(duration: 0.2), value: audioLevel)
}
Image(systemName: isMuted ? "mic.slash.fill" : "mic.fill")
.resizable()
.scaledToFit()
.frame(width: width * 0.2)
.foregroundColor(.white)
}
.frame(maxWidth: .infinity, maxHeight: .infinity) // Ensures the ZStack is centered
}
}
}
#Preview {
MicrophoneView(audioLevel: 1, isMuted: false)
}

View File

@@ -0,0 +1,31 @@
import SwiftUI
struct ToastModifier: ViewModifier {
var message: String?
var isShowing: Bool
func body(content: Content) -> some View {
ZStack {
content
if isShowing, let message = message {
VStack {
Text(message)
.padding()
.background(Color.black.opacity(0.7))
.foregroundColor(.white)
.cornerRadius(8)
.transition(.slide)
.padding(.top, 50)
Spacer()
}
.animation(.easeInOut(duration: 0.5), value: isShowing)
}
}
}
}
extension View {
func toast(message: String?, isShowing: Bool) -> some View {
self.modifier(ToastModifier(message: message, isShowing: isShowing))
}
}