iOS上使用WebRTC推拉流的案例

一、庫集成

首先,確保在你的 Podfile 中添加依賴:

pod 'GoogleWebRTC'

然后執行 pod install 安裝庫。

二、代碼示例

2.1、權限配置:在 Info.plist 中添加攝像頭、麥克風權限

<!-- 需要在 Info.plist 中添加以下權限 -->
<key>NSCameraUsageDescription</key>
<string>需要訪問攝像頭進行視頻通話</string>
<key>NSMicrophoneUsageDescription</key>
<string>需要訪問麥克風進行語音通話</string>
<key>NSAppTransportSecurity</key>
<dict><key>NSAllowsArbitraryLoads</key><true/>
</dict>

2.2、代碼
核心類oc:WebRTCManager.m

#import "WebRTCManager.h"
#import <AVFoundation/AVFoundation.h>@interface WebRTCManager ()
@property (nonatomic, strong) RTCPeerConnectionFactory *factory;
@property (nonatomic, strong) RTCVideoTrack *localVideoTrack;
@property (nonatomic, strong) RTCAudioTrack *localAudioTrack;
@property (nonatomic, strong) RTCVideoRendererAdapter *localRenderer;
@end@implementation WebRTCManager- (instancetype)initWithDelegate:(id<WebRTCManagerDelegate>)delegate {self = [super init];if (self) {_delegate = delegate;[self setupPeerConnectionFactory];[self setupPeerConnection];}return self;
}// 初始化 PeerConnection 工廠
- (void)setupPeerConnectionFactory {RTCInitializeSSL();_factory = [[RTCPeerConnectionFactory alloc] init];
}// 配置并創建 PeerConnection
- (void)setupPeerConnection {RTCConfiguration *config = [[RTCConfiguration alloc] init];config.iceServers = @[[[RTCIceServer alloc] initWithURLStrings:@[@"stun:stun.l.google.com:19302"]]];RTCOfferAnswerOptions *offerAnswerOptions = [[RTCOfferAnswerOptions alloc] init];offerAnswerOptions.offerToReceiveAudio = YES;offerAnswerOptions.offerToReceiveVideo = YES;RTCMediaConstraints *constraints = [[RTCMediaConstraints alloc] initWithMandatoryConstraints:@[] optionalConstraints:@[]];_peerConnection = [_factory peerConnectionWithConfiguration:config constraints:constraints delegate:self];
}// 初始化本地媒體流
- (void)setupLocalStreamWithVideoView:(UIView *)videoView {// 請求音視頻權限[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {dispatch_async(dispatch_get_main_queue(), ^{if (granted) {[self createLocalMediaStream];[self setupLocalVideoRender:videoView];} else {NSLog(@"需要攝像頭權限才能使用視頻功能");}});}];
}// 創建本地媒體流
- (void)createLocalMediaStream {_localStream = [_factory mediaStreamWithStreamId:@"localStream"];// 創建音頻軌道RTCAudioSource *audioSource = [_factory audioSourceWithConstraints:[self defaultMediaConstraints]];_localAudioTrack = [_factory audioTrackWithSource:audioSource trackId:@"audio0"];[_localStream addAudioTrack:_localAudioTrack];// 創建視頻軌道RTCVideoSource *videoSource = [_factory videoSourceWithConstraints:[self videoConstraints]];_localVideoTrack = [_factory videoTrackWithSource:videoSource trackId:@"video0"];[_localStream addVideoTrack:_localVideoTrack];// 將本地流添加到 PeerConnection[_peerConnection addStream:_localStream];
}// 設置本地視頻渲染
- (void)setupLocalVideoRender:(UIView *)videoView {RTCMTLVideoView *rendererView = [[RTCMTLVideoView alloc] init];rendererView.frame = videoView.bounds;rendererView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;[videoView addSubview:rendererView];_localRenderer = [[RTCVideoRendererAdapter alloc] initWithRenderer:rendererView];[_localVideoTrack addRenderer:_localRenderer];
}// 創建 Offer
- (void)createOffer {RTCOfferAnswerConstraints *constraints = [[RTCOfferAnswerConstraints alloc] init];constraints.mandatoryConstraints = @[[[RTCPair alloc] initWithKey:@"OfferToReceiveAudio" value:@"true"],[[RTCPair alloc] initWithKey:@"OfferToReceiveVideo" value:@"true"]];[_peerConnection createOfferWithConstraints:constraints completionHandler:^(RTCSessionDescription * _Nullable sdp, NSError * _Nullable error) {if (error) {NSLog(@"創建 Offer 失敗: %@", error.localizedDescription);return;}[self.peerConnection setLocalDescriptionWithSessionDescription:sdp completionHandler:^(NSError * _Nullable error) {if (error) {NSLog(@"設置本地描述失敗: %@", error.localizedDescription);return;}// 這里應該將 sdp 發送給信令服務器NSString *offerString = [self sessionDescriptionToString:sdp];NSLog(@"生成 Offer: %@", offerString);// [self.signalingClient sendOffer:offerString];}];}];
}// 處理遠程 Offer
- (void)handleRemoteOffer:(NSString *)offer {RTCSessionDescription *remoteSDP = [self stringToSessionDescription:offer type:RTCSdpTypeOffer];[_peerConnection setRemoteDescriptionWithSessionDescription:remoteSDP completionHandler:^(NSError * _Nullable error) {if (error) {NSLog(@"設置遠程 Offer 失敗: %@", error.localizedDescription);return;}// 創建 Answer[self createAnswer];}];
}// 創建 Answer
- (void)createAnswer {RTCOfferAnswerConstraints *constraints = [[RTCOfferAnswerConstraints alloc] init];[_peerConnection createAnswerWithConstraints:constraints completionHandler:^(RTCSessionDescription * _Nullable sdp, NSError * _Nullable error) {if (error) {NSLog(@"創建 Answer 失敗: %@", error.localizedDescription);return;}[self.peerConnection setLocalDescriptionWithSessionDescription:sdp completionHandler:^(NSError * _Nullable error) {if (error) {NSLog(@"設置本地 Answer 失敗: %@", error.localizedDescription);return;}// 將 Answer 發送給信令服務器NSString *answerString = [self sessionDescriptionToString:sdp];NSLog(@"生成 Answer: %@", answerString);// [self.signalingClient sendAnswer:answerString];}];}];
}// 處理遠程 Answer
- (void)handleRemoteAnswer:(NSString *)answer {RTCSessionDescription *remoteSDP = [self stringToSessionDescription:answer type:RTCSdpTypeAnswer];[_peerConnection setRemoteDescriptionWithSessionDescription:remoteSDP completionHandler:^(NSError * _Nullable error) {if (error) {NSLog(@"設置遠程 Answer 失敗: %@", error.localizedDescription);}}];
}// 處理遠程 ICE 候選者
- (void)handleRemoteICECandidate:(NSString *)candidate {NSDictionary *candidateDict = [NSJSONSerialization JSONObjectWithData:[candidate dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil];if (!candidateDict) return;RTCIceCandidate *iceCandidate = [[RTCIceCandidate alloc] initWithSdpMLineIndex:[candidateDict[@"sdpMLineIndex"] integerValue]sdpMid:candidateDict[@"sdpMid"]sdp:candidateDict[@"sdp"]];[_peerConnection addIceCandidate:iceCandidate completionHandler:^(NSError * _Nullable error) {if (error) {NSLog(@"添加遠程 ICE 候選者失敗: %@", error.localizedDescription);}}];
}// 斷開連接
- (void)disconnect {[_peerConnection close];_peerConnection = nil;[_localStream removeAllAudioTracks];[_localStream removeAllVideoTracks];_localStream = nil;
}#pragma mark - RTCPeerConnectionDelegate// 收到遠程媒體流
- (void)peerConnection:(RTCPeerConnection *)peerConnection didAddStream:(RTCMediaStream *)stream {NSLog(@"收到遠程媒體流");if ([self.delegate respondsToSelector:@selector(didReceiveRemoteStream:)] && stream) {[self.delegate didReceiveRemoteStream:stream];}
}// 生成 ICE 候選者
- (void)peerConnection:(RTCPeerConnection *)peerConnection didGenerateIceCandidate:(RTCIceCandidate *)candidate {NSDictionary *candidateDict = @{@"sdpMLineIndex": @(candidate.sdpMLineIndex),@"sdpMid": candidate.sdpMid ?: @"",@"sdp": candidate.sdp};NSData *jsonData = [NSJSONSerialization dataWithJSONObject:candidateDict options:0 error:nil];NSString *candidateString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];// 將 ICE 候選者發送給信令服務器NSLog(@"生成 ICE 候選者: %@", candidateString);// [self.signalingClient sendICECandidate:candidateString];
}#pragma mark - 工具方法// 媒體約束配置
- (RTCMediaConstraints *)defaultMediaConstraints {return [[RTCMediaConstraints alloc] initWithMandatoryConstraints:@[] optionalConstraints:@[]];
}// 視頻約束配置
- (RTCMediaConstraints *)videoConstraints {return [[RTCMediaConstraints alloc] initWithMandatoryConstraints:@[[[RTCPair alloc] initWithKey:@"maxWidth" value:@"1280"],[[RTCPair alloc] initWithKey:@"maxHeight" value:@"720"],[[RTCPair alloc] initWithKey:@"maxFrameRate" value:@"30"]] optionalConstraints:@[]];
}// 將 SessionDescription 轉換為字符串
- (NSString *)sessionDescriptionToString:(RTCSessionDescription *)sdp {NSDictionary *dict = @{@"type": [self sdpTypeToString:sdp.type],@"sdp": sdp.sdp};NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dict options:0 error:nil];return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
}// 將字符串轉換為 SessionDescription
- (RTCSessionDescription *)stringToSessionDescription:(NSString *)string type:(RTCSdpType)type {NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];return [[RTCSessionDescription alloc] initWithType:type sdp:dict[@"sdp"]];
}// SDP 類型轉換
- (NSString *)sdpTypeToString:(RTCSdpType)type {switch (type) {case RTCSdpTypeOffer: return @"offer";case RTCSdpTypeAnswer: return @"answer";case RTCSdpTypePrAnswer: return @"pranswer";case RTCSdpTypeRollback: return @"rollback";default: return @"";}
}@end

調用OC:ViewController.m

#import "ViewController.h"
#import <WebRTC/WebRTC.h>@interface ViewController ()
@property (nonatomic, strong) WebRTCManager *rtcManager;
@property (nonatomic, strong) UIView *localVideoView;
@property (nonatomic, strong) UIView *remoteVideoView;
@property (nonatomic, strong) UIButton *connectButton;
@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];self.view.backgroundColor = UIColor.whiteColor;[self setupUI];[self setupWebRTC];
}- (void)setupUI {// 本地視頻視圖_localVideoView = [[UIView alloc] init];_localVideoView.backgroundColor = UIColor.lightGrayColor;_localVideoView.translatesAutoresizingMaskIntoConstraints = NO;[self.view addSubview:_localVideoView];// 遠程視頻視圖_remoteVideoView = [[UIView alloc] init];_remoteVideoView.backgroundColor = UIColor.darkGrayColor;_remoteVideoView.translatesAutoresizingMaskIntoConstraints = NO;[self.view addSubview:_remoteVideoView];// 連接按鈕_connectButton = [UIButton buttonWithType:UIButtonTypeSystem];[_connectButton setTitle:@"建立連接" forState:UIControlStateNormal];[_connectButton addTarget:self action:@selector(connectButtonTapped) forControlEvents:UIControlEventTouchUpInside];_connectButton.translatesAutoresizingMaskIntoConstraints = NO;[self.view addSubview:_connectButton];// 布局[NSLayoutConstraint activateConstraints:@[// 本地視頻(右上角小窗口)[_localVideoView topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor constant:20],[_localVideoView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor constant:-20],[_localVideoView.widthAnchor constraintEqualToConstant:120],[_localVideoView.heightAnchor constraintEqualToConstant:180],// 遠程視頻(全屏)[_remoteVideoView.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor],[_remoteVideoView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],[_remoteVideoView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],[_remoteVideoView.bottomAnchor constraintEqualToAnchor:self.connectButton.topAnchor constant:-20],// 連接按鈕[_connectButton.bottomAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor constant:-20],[_connectButton.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor],[_connectButton.widthAnchor constraintEqualToConstant:120],[_connectButton.heightAnchor constraintEqualToConstant:44]]];
}- (void)setupWebRTC {_rtcManager = [[WebRTCManager alloc] initWithDelegate:self];[_rtcManager setupLocalStreamWithVideoView:self.localVideoView];
}- (void)connectButtonTapped {[_rtcManager createOffer];
}#pragma mark - WebRTCManagerDelegate// 處理收到的遠程流
- (void)didReceiveRemoteStream:(RTCMediaStream *)stream {dispatch_async(dispatch_get_main_queue(), ^{// 渲染遠程視頻if (stream.videoTracks.count > 0) {RTCVideoTrack *remoteVideoTrack = stream.videoTracks[0];RTCMTLVideoView *rendererView = [[RTCMTLVideoView alloc] init];rendererView.frame = self.remoteVideoView.bounds;rendererView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;[self.remoteVideoView addSubview:rendererView];[remoteVideoTrack addRenderer:[[RTCVideoRendererAdapter alloc] initWithRenderer:rendererView]];}});
}- (void)dealloc {[_rtcManager disconnect];
}@end

代碼說明
這個示例實現了 iOS 平臺上基于 WebRTC 的基本音視頻推拉流功能,主要包含以下部分:
WebRTCManager:核心管理類,負責:

  • 初始化 WebRTC 相關組件
  • 處理本地音視頻流的捕獲和預覽
  • 管理 PeerConnection 連接
  • 處理 SDP 交換和 ICE 候選者

ViewController:界面控制器,負責:

  • 創建本地和遠程視頻的預覽視圖
  • 處理用戶交互(如建立連接)
  • 渲染遠程視頻流

三、使用說明

這個示例缺少信令服務器的實現,你需要自己搭建一個信令服務器
在實際使用中,需要將代碼中注釋掉的信令發送部分替換為實際的網絡請求
代碼中的 STUN 服務器使用了 Google 的公共服務器,生產環境中建議使用自己的 STUN/TURN 服務器

四、擴展建議

  • 添加錯誤處理和重連機制
  • 實現多人通話功能
  • 添加視頻質量控制
  • 實現屏幕共享功能
  • 添加音頻 /video 開關控制

這個示例僅僅提供了基礎的推拉流框架,你可以根據實際需求進行擴展和優化。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/916224.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/916224.shtml
英文地址,請注明出處:http://en.pswp.cn/news/916224.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

API: return response as HTML table

想要把response table變成HTML的table&#xff0c;即想達到下面這種的話<table boarder"1" style"width:100%; boarder-collapse: collapse; text-align:left"><tr><th>Customer</th><th>Date</th><th>Debit Am…

OneNote 當前無法同步筆記。將繼續嘗試。 (錯誤代碼: 0xE00009C8 bb0ur)問題解決

之前因為同步錯誤&#xff0c;導致OneNote一個筆記本內容全部消失&#xff0c;筆記本內容如下圖同步狀態和錯誤如下&#xff1a;提醒錯誤為&#xff1a;OneNote 當前無法同步筆記。將繼續嘗試。 (錯誤代碼: 0xE00009C8 bb0ur)當時心態有點崩&#xff0c;也是查了好些資料&#…

OneCode3.0 Gallery 組件前后端映射機制:從注解配置到前端渲染的完整鏈路

一、注解體系與前端組件的映射基礎 ? OneCode Gallery 組件實現了 Java 注解與前端 UI 組件的深度綁定&#xff0c;通過GalleryAnnotation、GalleryItemAnnotation和GalleryViewAnnotation三個核心注解&#xff0c;構建了從后端配置到前端渲染的完整鏈路。這種映射機制的核心價…

規則分配腳本

需求&#xff1a; 1.根據用戶編寫的要報規則,去mysql庫里SysManage_Rule表獲取已經啟用的規則作為條件&#xff08;例如[{“field”: “關鍵詞”, “logic”: “AND”, “value”: “阿爾法”, “operator”: “”&#xff0c;, “assign_user”: “user222”}]&#xff09;條…

SEO實戰派白楊SEO:SEO中說的框計算、知心搜索(知識圖譜)是什么?有什么用處?

SEO里框計算是什么&#xff1f;有什么用處&#xff1f;SEO里框計劃算是百度2010年提出的&#xff0c;指當用戶搜索某些關鍵詞查詢時&#xff0c;搜索引擎在結果頁直接展示答案的技術&#xff08;如天氣、匯率等&#xff09;&#xff0c;用戶無需點擊網站即可獲取信息&#xff0…

軟件工程:軟件需求

簡介本篇博客記錄了我在軟件工程學習過程中關于軟件需求與面向對象基礎知識的學習體會和要點總結。博客共分為三個關卡內容&#xff1a;第1關圍繞“軟件需求”的定義、分類及分析過程展開&#xff0c;讓我清晰地理解了功能性需求、非功能性需求與約束條件的區別&#xff1b;第2…

MES系統是什么,有哪些特性?

MES系統是一套面向制造企業車間執行層的生產信息化管理系統。它能夠為操作人員和管理人員提供計劃的執行、跟蹤以及所有資源&#xff08;包括人、設備、物料、客戶需求等&#xff09;的當前狀態。通過MES系統可以對從訂單下達到產品完成的整個生產過程進行優化管理。當工廠發生…

Vue2下

六&#xff1a;vue-router &#xff08;重要&#xff09; &#xff08;一&#xff09;. 對路由的理解 1.什么是路由 路由&#xff08;Router&#xff09; 是管理頁面跳轉和 URL 與視圖映射關系的機制&#xff0c;核心作用是&#xff1a;根據不同的 URL 路徑&#xff0c;展示對…

在 Windows 上安裝設置 MongoDB及常見問題

介紹 MongoDB 是一個開源的 NoSQL 數據庫系統&#xff0c;它以一種靈活的類似 JSON 的格式&#xff08;稱為 BSON&#xff08;二進制 JSON&#xff09;&#xff09;存儲數據。它使用動態模式&#xff0c;這意味著與關系型數據庫不同&#xff0c;MongoDB 不需要在向數據庫添加數…

Effective C++ 條款01:視 C++ 為一個語言聯邦

Effective C 條款01&#xff1a;視 C 為一個語言聯邦核心思想&#xff1a;C 是由多個子語言組成的聯邦&#xff0c;每個子語言有自己的編程范式。理解這些子語言及其規則切換&#xff0c;是寫出高效 C 代碼的關鍵。 四個子語言及其規則&#xff1a; C 語言 基礎&#xff1a;過程…

云效CI/CD教程(PHP項目)

參考文檔 參考云效的官方文檔https://help.aliyun.com/zh/yunxiao/ 一、新建代碼庫 這是第一步&#xff0c;和碼云的差不多 二、配SSH密鑰 這個和碼云&#xff0c;github上類似&#xff0c;都需要&#xff0c;云效的SSH密鑰證書不是采用 RSA算法&#xff0c;而是采用了ED2…

單片機是怎么控制的

單片機作為電子系統的控制核心&#xff0c;通過接收外部信號、執行預設程序、驅動外部設備的方式實現控制功能&#xff0c;其控制過程涉及信號輸入、數據處理和指令輸出三個關鍵環節&#xff0c;每個環節的協同配合決定了整體控制效果。 信號輸入&#xff1a;獲取外部信息 單片…

deepseek本地部署,輕松實現編程自由

小伙伴們&#xff0c;大家好&#xff0c;今天我們來實現deepseek本地部署&#xff0c;輕松實現編程自由&#xff01;安裝ollama 安裝ollama 首先我們安裝ollama 打開ollama官網&#xff0c;下載安裝符合自己系統的版本。 找到要安裝的模型deepseek-r1開始-運行 輸入cmd出現…

基礎NLP | 常用工具

編輯器 PycharmVSCodeSpyderPython 自帶 ideVim 機器學習相關python框架 Pytorch 學術界寵兒&#xff0c;調試方便&#xff0c;目前的主流Tensorflow 大名鼎鼎&#xff0c;工程配套完善Keras 高級封裝&#xff0c;簡單好用&#xff0c;現已和Tensorflow合體Gensim 訓練詞向…

Unity3D + VR頭顯 × RTSP|RTMP播放器:構建沉浸式遠程診療系統的技術實踐

一、背景&#xff1a;遠程醫療邁入“沉浸式協同”的新階段 過去&#xff0c;遠程醫療主要依賴視頻會議系統&#xff0c;實現基礎的遠程問診、會診或術中指導。雖然初步解決了地域限制問題&#xff0c;但其單視角、平面化、缺乏沉浸感與交互性的特征&#xff0c;已無法滿足臨床…

海云安斬獲“智能金融創新應用“標桿案例 彰顯AI安全左移技術創新實力

近日&#xff0c;由中國人民銀行廣東省分行、廣東省金融管理局、廣東省政務服務和數據管理局指導&#xff0c;廣東省金融科技協會主辦的“智能金融 創新應用”優秀案例名單最終揭曉&#xff0c;海云安開發者安全助手系統項目憑借其創新的"AI安全左移"技術架構&#x…

Fluent許可與網絡安全策略

在流體動力學模擬領域&#xff0c;Fluent軟件因其卓越的性能和廣泛的應用而備受用戶青睞。然而&#xff0c;隨著網絡安全威脅的不斷增加&#xff0c;確保Fluent許可的安全性和合規性變得尤為重要。本文將探討Fluent許可與網絡安全策略的關系&#xff0c;為您提供一套有效的安全…

如何借助AI工具?打贏通信設備制造的高風險之戰?(案例分享)

你是否曾在項目管理中遇到過那種讓人心跳加速的瞬間&#xff0c;當一項風險突然暴露出來時&#xff0c;全隊似乎都屏住了呼吸&#xff1f;今天&#xff0c;我就來分享一個我親歷的項目案例&#xff0c;講述我們如何借助具體的AI工具&#xff0c;實現從數據到決策的華麗轉變&…

Web服務器(Tomcat、項目部署)

1. 簡介 1.1 什么是Web服務器 Web服務器是一個應用程序&#xff08;軟件&#xff09;&#xff0c;對HTTP協議的操作進行封裝&#xff0c;使得程序員不必直接對協議進行操作&#xff0c;讓Web開發更加便捷。主要功能是"提供網上信息瀏覽服務"。 Web服務器是安裝在服…

list 介紹 及 底層

list的相關文檔&#xff1a;list - C Reference 一、list的介紹及使用 list中的接口比較多&#xff0c;此處類似&#xff0c;只需要掌握如何正確的使用&#xff0c;然后再去深入研究背后的原理&#xff0c;已達到可擴展的能力。以下為list中一些常見的重要接口。我們庫里的list…