Skip to content

Commit

Permalink
feat: video chat
Browse files Browse the repository at this point in the history
  • Loading branch information
niranjannitesh committed Nov 7, 2024
1 parent 89fa78b commit 34c5082
Show file tree
Hide file tree
Showing 11 changed files with 1,230 additions and 697 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<Bucket
uuid = "D0AFB9E6-D974-41B0-8E7A-B0C0AB5FC685"
type = "0"
version = "2.0">
<Breakpoints>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "A956974E-9F4C-46AF-9BFF-FFDBCF4B71FB"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "Kino/KinoApp.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "50"
endingLineNumber = "50"
landmarkName = "ChatViewModel"
landmarkType = "3">
</BreakpointContent>
</BreakpointProxy>
</Breakpoints>
</Bucket>
4 changes: 4 additions & 0 deletions Kino/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
</dict>
<key>LSMultipleInstancesProhibited</key>
<false/>
<key>NSCameraUsageDescription</key>
<string>Camera access is needed for video chat with other participants</string>
<key>NSMicrophoneUsageDescription</key>
<string>Microphone access is needed for voice chat with other participants</string>
<key>UTImportedTypeDeclarations</key>
<array>
<dict>
Expand Down
112 changes: 93 additions & 19 deletions Kino/KinoApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,26 @@

import SwiftUI
import VLCKit
import WebRTC


struct ChatMessage: Identifiable {
let id = UUID()
let text: String
let sender: String
let time: String
let isSent: Bool
}

struct Participant: Identifiable {
let id: UUID
var name: String
var status: String
let avatar: String
var isAudioEnabled: Bool
var videoTrack: RTCVideoTrack?
var isLocal: Bool
}


struct PlayerState: Codable {
Expand All @@ -26,46 +46,101 @@ enum KinoScreen {
case player
}

@Observable
class ChatViewModel {
var participants: [Participant] = []

let messages = [
ChatMessage(text: "This scene is amazing!", sender: "Sarah", time: "2m ago", isSent: false),
ChatMessage(
text: "Yeah, the cinematography is incredible", sender: "You", time: "1m ago", isSent: true),
ChatMessage(
text: "The score really adds to the tension", sender: "Alex", time: "Just now", isSent: false),
ChatMessage(
text: "Definitely! This is my favorite part coming up", sender: "You", time: "Just now",
isSent: true),
]

func updateParticipantVideo(id: UUID, track: RTCVideoTrack?) {
DispatchQueue.main.async {
RTCLogger.shared.log("Participants", "Updating video track for participant: \(id)")

if let index = self.participants.firstIndex(where: { $0.id == id }) {
var participant = self.participants[index]
participant.videoTrack = track
self.participants[index] = participant
RTCLogger.shared.log("Participants", "Video track updated successfully")
} else {
RTCLogger.shared.log("Participants", "Warning: No participant found for video track update")
}
}
}

func updateOrAddParticipant(_ participant: Participant) {
DispatchQueue.main.async {
RTCLogger.shared.log("Participants", "Updating/adding participant: \(participant.id)")

if let index = self.participants.firstIndex(where: { $0.id == participant.id }) {
// Update existing participant
var updatedParticipant = self.participants[index]

// Preserve video track if new one is nil
if participant.videoTrack != nil {
updatedParticipant.videoTrack = participant.videoTrack
}

updatedParticipant.name = participant.name
updatedParticipant.status = participant.status
updatedParticipant.isAudioEnabled = participant.isAudioEnabled

RTCLogger.shared.log("Participants", "Updated existing participant: \(updatedParticipant.id)")
self.participants[index] = updatedParticipant
} else {
// Add new participant
RTCLogger.shared.log("Participants", "Adding new participant: \(participant.id)")
self.participants.append(participant)
}
}
}
}

@Observable
class RoomViewModel {
private let webRTCService: WebRTCService
private var lastSyncTime: TimeInterval = 0
let fileStreamManager: FileStreamManager
var isInternalStateChange = false

var roomCode: String = ""
var isHost: Bool = false
var isConnected: Bool = false
var error: String?

init() {
webRTCService = WebRTCService()
fileStreamManager = FileStreamManager(webRTCService: webRTCService)
}

// Create a new room
func createRoom() async {

func createRoom(displayName: String) async {
do {
isHost = true
roomCode = try await webRTCService.createRoom()
roomCode = try await webRTCService.createRoom(displayName: displayName)
} catch {
self.error = "Failed to create room: \(error.localizedDescription)"
}
}

// Join existing room
func joinRoom(code: String) async {

func joinRoom(code: String, displayName: String) async {
do {
isHost = false
roomCode = code
try await webRTCService.joinRoom(code: code)
try await webRTCService.joinRoom(code: code, displayName: displayName)
} catch {
self.error = "Failed to join room: \(error.localizedDescription)"
}
}


// Handle player state changes


func handlePlayerStateChange(state: PlayerState) {
guard !isInternalStateChange else { return }
let currentTime = Date().timeIntervalSince1970
Expand All @@ -78,7 +153,7 @@ class RoomViewModel {
lastSyncTime = currentTime
}
}

func setPlayerDelegate(_ delegate: WebRTCServiceDelegate) {
webRTCService.delegate = delegate
}
Expand All @@ -87,17 +162,16 @@ class RoomViewModel {
@Observable
class KinoViewModel {
var roomViewModel = RoomViewModel()

var chatViewModel = ChatViewModel()

var currentScreen: KinoScreen = .home
var showNewRoomSheet = false
var showJoinSheet = false
var roomName = ""
var displayName = ""


var isInRoom: Bool {
!roomViewModel.roomCode.isEmpty
}

// Leave room function
func leaveRoom() {
roomViewModel.roomCode = ""
Expand Down
52 changes: 50 additions & 2 deletions Kino/Service/SignalingService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,19 @@
import Foundation
import WebRTC

struct ParticipantInfo: Codable {
let id: UUID
let name: String
let isHost: Bool
}

struct TrackInfo: Codable {
let participantId: UUID
let videoTrackId: String
let audioTrackId: String
let participantInfo: ParticipantInfo
}

// MARK: - Signaling Models
struct SignalingMessage: Codable {
enum MessageType: String, Codable {
Expand All @@ -16,6 +29,7 @@ struct SignalingMessage: Codable {
case iceCandidate
case join
case leave
case trackInfo
}

let type: MessageType
Expand All @@ -27,6 +41,8 @@ enum SignalingPayload: Codable {
case sdp(SDPMessage)
case ice(ICEMessage)
case plain(String)
case trackInfo(TrackInfo)


private enum CodingKeys: String, CodingKey {
case type, data
Expand All @@ -43,6 +59,9 @@ enum SignalingPayload: Codable {
case "ice":
let ice = try container.decode(ICEMessage.self, forKey: .data)
self = .ice(ice)
case "trackInfo":
let trackInfo = try container.decode(TrackInfo.self, forKey: .data)
self = .trackInfo(trackInfo)
default:
let string = try container.decode(String.self, forKey: .data)
self = .plain(string)
Expand All @@ -59,6 +78,9 @@ enum SignalingPayload: Codable {
case .ice(let ice):
try container.encode("ice", forKey: .type)
try container.encode(ice, forKey: .data)
case .trackInfo(let trackInfo):
try container.encode("trackInfo", forKey: .type)
try container.encode(trackInfo, forKey: .data)
case .plain(let string):
try container.encode("plain", forKey: .type)
try container.encode(string, forKey: .data)
Expand Down Expand Up @@ -190,8 +212,33 @@ class SignalingService {
return
}

// Handle forwarded offer/ice messages
if type == "offer" {
if type == "trackInfo" {
if let payload = json["payload"] as? [String: Any],
let trackData = payload["data"] as? [String: Any],
let participantId = UUID(uuidString: trackData["participantId"] as? String ?? ""),
let videoTrackId = trackData["videoTrackId"] as? String,
let audioTrackId = trackData["audioTrackId"] as? String,
let participantData = trackData["participantInfo"] as? [String: Any],
let name = participantData["name"] as? String,
let isHost = participantData["isHost"] as? Bool {

RTCLogger.shared.log("Signaling", "Processing forwarded track info")
let participantInfo = ParticipantInfo(
id: participantId,
name: name,
isHost: isHost
)

let trackInfo = TrackInfo(
participantId: participantId,
videoTrackId: videoTrackId,
audioTrackId: audioTrackId,
participantInfo: participantInfo
)

delegate?.signaling(didReceiveTrackInfo: trackInfo, for: currentRoomCode!)
}
} else if type == "offer" {
if let payload = json["payload"] as? [String: Any],
let sdpData = payload["data"] as? [String: Any],
let sdp = sdpData["sdp"] as? String
Expand Down Expand Up @@ -259,4 +306,5 @@ protocol SignalingServiceDelegate: AnyObject {
func signaling(didReceiveAnswer: SDPMessage, for roomCode: String)
func signaling(didReceiveIceCandidate: ICEMessage, for roomCode: String)
func signaling(didReceiveJoin: String, for roomCode: String)
func signaling(didReceiveTrackInfo: TrackInfo, for roomCode: String)
}
Loading

0 comments on commit 34c5082

Please sign in to comment.