Files
client/file_answer.go
2025-07-13 22:30:33 +05:30

181 lines
5.1 KiB
Go

package client
import (
"context"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
"time"
"github.com/pion/webrtc/v4"
)
// FileAnswerSignal implements BaseSignal interface for file-based signaling (answer side)
type FileAnswerSignal struct {
peerConnection *PeerConnection
ctx context.Context
offerPath string
answerPath string
}
// CreateFileAnswerSignal creates a new FileAnswerSignal
func CreateFileAnswerSignal(ctx context.Context, peerConnection *PeerConnection, offerPath string, answerPath string) *FileAnswerSignal {
return &FileAnswerSignal{
peerConnection: peerConnection,
ctx: ctx,
offerPath: offerPath,
answerPath: answerPath,
}
}
// Connect implements the BaseSignal interface
func (signal *FileAnswerSignal) Connect(category, connectionLabel string) error {
// Use category and connectionLabel to create unique filenames if needed
if category != "" && connectionLabel != "" {
signal.offerPath = filepath.Join(signal.offerPath, category, connectionLabel, "offer.txt")
signal.answerPath = filepath.Join(signal.answerPath, category, connectionLabel, "answer.txt")
// Ensure directory exists
dir := filepath.Dir(signal.answerPath)
if err := os.MkdirAll(dir, 0755); err != nil {
return fmt.Errorf("error creating directory %s: %w", dir, err)
}
}
// Wait for offer file to exist
offer, err := signal.waitForOffer()
if err != nil {
return err
}
// Process the offer
return signal.processOffer(offer)
}
// waitForOffer waits for the offer file to appear and loads it
func (signal *FileAnswerSignal) waitForOffer() (webrtc.SessionDescription, error) {
var offer webrtc.SessionDescription
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
timeout := time.NewTimer(5 * time.Minute)
defer timeout.Stop()
fmt.Printf("Waiting for offer file at %s...\n", signal.offerPath)
for {
select {
case <-ticker.C:
// Check if offer file exists
if _, err := os.Stat(signal.offerPath); err == nil {
// Load offer from file
offer, err := signal.loadSDPFromFile(signal.offerPath)
if err != nil {
fmt.Printf("Error loading offer: %v, retrying...\n", err)
continue
}
return offer, nil
}
case <-timeout.C:
return offer, errors.New("timeout waiting for offer file")
case <-signal.ctx.Done():
return offer, errors.New("context canceled")
}
}
}
// processOffer processes the offer and creates an answer
func (signal *FileAnswerSignal) processOffer(offer webrtc.SessionDescription) error {
// SetInputOption remote description
if err := signal.peerConnection.peerConnection.SetRemoteDescription(offer); err != nil {
return fmt.Errorf("error setting remote description: %w", err)
}
// Create answer
answer, err := signal.peerConnection.peerConnection.CreateAnswer(nil)
if err != nil {
return fmt.Errorf("error creating answer: %w", err)
}
// SetInputOption local description
if err := signal.peerConnection.peerConnection.SetLocalDescription(answer); err != nil {
return fmt.Errorf("error setting local description: %w", err)
}
// Wait for ICE gathering to complete
timer := time.NewTicker(30 * time.Second)
defer timer.Stop()
select {
case <-webrtc.GatheringCompletePromise(signal.peerConnection.peerConnection):
fmt.Println("ICE Gathering complete")
case <-timer.C:
return errors.New("failed to gather ICE candidates within 30 seconds")
}
// Save answer to file
if err := signal.saveSDPToFile(signal.peerConnection.peerConnection.LocalDescription(), signal.answerPath); err != nil {
return fmt.Errorf("error saving answer to file: %w", err)
}
fmt.Printf("Answer saved to %s\n", signal.answerPath)
return nil
}
// Close implements the BaseSignal interface
func (signal *FileAnswerSignal) Close() error {
// Nothing to close for file-based signaling
return nil
}
// saveSDPToFile saves a SessionDescription to a file
func (signal *FileAnswerSignal) saveSDPToFile(sdp *webrtc.SessionDescription, filename string) error {
// Ensure directory exists
dir := filepath.Dir(filename)
if err := os.MkdirAll(dir, 0755); err != nil {
return fmt.Errorf("error creating directory %s: %w", dir, err)
}
// Encode SDP
b, err := json.Marshal(sdp)
if err != nil {
return err
}
encoded := base64.StdEncoding.EncodeToString(b)
// Write to file
if err := os.WriteFile(filename, []byte(encoded), 0644); err != nil {
return err
}
fmt.Printf("SDP saved to %s (%d bytes)\n", filename, len(encoded))
return nil
}
// loadSDPFromFile loads a SessionDescription from a file
func (signal *FileAnswerSignal) loadSDPFromFile(filename string) (webrtc.SessionDescription, error) {
var sdp webrtc.SessionDescription
data, err := os.ReadFile(filename)
if err != nil {
return sdp, fmt.Errorf("error reading %s: %w", filename, err)
}
encoded := string(data)
b, err := base64.StdEncoding.DecodeString(encoded)
if err != nil {
return sdp, fmt.Errorf("base64 decode error: %w", err)
}
if err = json.Unmarshal(b, &sdp); err != nil {
return sdp, fmt.Errorf("JSON unmarshal error: %w", err)
}
fmt.Printf("SDP loaded from %s (%d bytes)\n", filename, len(data))
return sdp, nil
}