...
Данный пример может использоваться как стример для публикации WebRTC-видеопотока с экрана и отдельного аудиопотока для голосового сопровожденияс захватом микрофона или системного звука. Пример работает с iOS SDK 2.6.82 и новее.
На скриншоте ниже представлен общий вид приложения. Поля ввода:
- WCS Websocket URL
- имя видео потока с экрана
- имя аудио потока с микрофона
Экран приложения в начале публикации
Для захвата экрана используется отдельный процесс расширения, который работает до тех пор, пока устройство не будет заблокировано или пока публикация экрана не будет остановлена.
Работа с кодом примера
Для разбора кода возьмем версию примера ScreenCapturer, которая доступна для скачивания на GitHub.
Классы
- класс для основного вида приложения: ScreenCapturerViewController (файл имплементации ScreenCapturerViewController.swift)
- класс реализации расширения: ScreenCapturerExtensionHandler (файл имплементации ScreenCapturerExtensionHandler.swift)
1. Импорт API
Code Block | ||||
---|---|---|---|---|
| ||||
import FPWCSApi2Swift |
2. Создание сессии для публикации аудио
WCSSession, WCSSession.connect code
В параметрах сессии указываются:
...
Настройка параметров расширения для захвата экрана
Параметр UserDefaults.suiteName должен совпадать с идентификатором группы расширения
Code Block | ||||
---|---|---|---|---|
| ||||
@IBAction func publishAudioPressedbroadcastBtnPressed(_ sender: Any) { if (publishAudioButton.title(for: .normal) == "Publish Audio") { ... pickerView.showsMicrophoneButton = systemOrMicSwitch.isOn let optionsuserDefaults = FPWCSApi2SessionOptions(UserDefaults.init(suiteName: "group.com.flashphoner.ScreenCapturerSwift") userDefaults?.set(urlField.text, forKey: "wcsUrl") options.urlServer = self.urlField.textuserDefaults?.set(publishVideoName.text, forKey: "streamName") userDefaults?.set(systemOrMicSwitch.isOn, forKey: "useMic") options.appKey = "defaultApp" ... } |
3. Получение параметров захвата экрана в расширении
Code Block | ||||
---|---|---|---|---|
| ||||
override func broadcastStarted(withSetupInfo setupInfo: [String : do NSObject]?) { ... let userDefaults = try session = WCSSession(options) UserDefaults.init(suiteName: "group.com.flashphoner.ScreenCapturerSwift") let wcsUrl = userDefaults?.string(forKey: "wcsUrl") } catchif { wcsUrl != self.wcsUrl || session?.getStatus() != .fpwcsSessionStatusEstablished { printsession?.disconnect(error) session = }nil } self... wcsUrl = wcsUrl ?? self.wcsUrl session?.connect() let streamName changeViewState(publishAudioButton, false= userDefaults?.string(forKey: "streamName") }self.streamName else= { streamName ?? self.streamName ... } } |
3. Публикация аудио
WCSSession.createStream, WCSStream.publish code
Методу createStream передаются параметры:
- имя публикуемого потока
- ограничения для захвата только аудио
4. Настройка для захвата звука при публикации экрана
FPWCSApi2.getAudioManager().useAudioModule code
Code Block | ||||
---|---|---|---|---|
| ||||
func onConnected(_ session:WCSSession) throws { let optionsuseMic = FPWCSApi2StreamOptions() options.name = publishAudioName.textuserDefaults?.bool(forKey: "useMic") options.constraints capturer = FPWCSApi2MediaConstraintsScreenRTCVideoCapturer(audiouseMic: true, video: false); do {useMic ?? true) publishStream = try session.createStream(options) FPWCSApi2.getAudioManager().useAudioModule(true) |
5. Создание сессии для публикации экрана
WCSSession, WCSSession.connect code
Code Block | ||||
---|---|---|---|---|
| ||||
if (session == nil) } catch { let options = printFPWCSApi2SessionOptions(error); }options.urlServer = self.wcsUrl ...options.appKey = "defaultApp" do { try publishStream?.publish(session = WCSSession(options) } catch { print(error); } } |
4. Настройка параметров расширения для захвата экрана
Параметр UserDefaults.suiteName должен совпадать с идентификатором группы расширения
Code Block | ||||
---|---|---|---|---|
| ||||
@objc func pickerAction() { //#WCS-3207 - Use suite name as group id in entitlements let userDefaults = UserDefaults.init(suiteName: "group.com.flashphoner.ScreenCapturerSwift") userDefaults?.set(urlField.text, forKey: "wcsUrl") userDefaultssession?.set(publishVideoName.text, forKey: "streamName"connect() } |
5. Настройка класса для захвата экрана
Code Block | ||||
---|---|---|---|---|
| ||||
fileprivate var capturer: ScreenRTCVideoCapturer = ScreenRTCVideoCapturer() |
6. Получение параметров захвата экрана
code6. Публикация потока с экрана
WCSSession.createStream, WCSStream.publish code
Методу createStream передаются параметры:
- имя публикуемого потока
- объект ScreenRTCVideoCapturer для захвата потока с экрана
Code Block | ||||
---|---|---|---|---|
| ||||
override func broadcastStartedonConnected(withSetupInfo_ setupInfosession: [String : NSObject]?) { WCSSession) throws { let options = //#WCS-3207 - Use suite name as group id in entitlementsFPWCSApi2StreamOptions() options.name = streamName let userDefaultsoptions.constraints = UserDefaults.init(suiteName: "group.com.flashphoner.ScreenCapturerSwift") FPWCSApi2MediaConstraints(audio: false, videoCapturer: capturer); lettry wcsUrlpublishStream = userDefaults?session.string(forKey: "wcsUrl"createStream(options) if... wcsUrl != self.wcsUrl || session?.getStatus() != .fpwcsSessionStatusEstablished { try sessionpublishStream?.disconnectpublish() } |
7. Инициализация класса ScreenRTCVideoCapturer
Code Block | ||||
---|---|---|---|---|
| ||||
fileprivate class ScreenRTCVideoCapturer: RTCVideoCapturer { let sessionkNanosecondsPerSecond = nil1000000000 var useMic: Bool = } self.wcsUrl = wcsUrl ?? self.wcsUrltrue; init(useMic: Bool) { let streamName = userDefaults?.string(forKey: "streamName"super.init() self.streamNameuseMic = streamName ?? self.streamNameuseMic } ... } |
7. Создание сессии для публикации экрана
WCSSession, WCSSession.connect 8. Захват системного аудио в расширении
FPWCSApi2.getAudioManager().getAudioModule().deliverRecordedData() code
Code Block | ||||
---|---|---|---|---|
| ||||
func processSampleBuffer(_ sampleBuffer: CMSampleBuffer, ifwith (session == nilsampleBufferType: RPSampleBufferType) { switch let options = FPWCSApi2SessionOptions()sampleBufferType { options.urlServer = self.wcsUrl... case options.appKey = "defaultApp" RPSampleBufferType.audioApp: if (!useMic) do { try session = WCSSession(optionsFPWCSApi2.getAudioManager().getAudioModule().deliverRecordedData(sampleBuffer) } catch { print(error) break } ... session?.connect() } |
8. Публикация потока с экрана
WCSSession.createStream, WCSStream.publish code
Методу createStream передаются параметры:
- имя публикуемого потока
- объект ScreenRTCVideoCapturer для захвата потока с экрана
}
} |
9. Захват аудио с микрофона в расширении
FPWCSApi2.getAudioManager().getAudioModule().deliverRecordedData() code
Code Block | ||||
---|---|---|---|---|
| ||||
func onConnectedprocessSampleBuffer(_ session:WCSSessionsampleBuffer: CMSampleBuffer, with sampleBufferType: RPSampleBufferType) throws { letswitch options = FPWCSApi2StreamOptions()sampleBufferType { options.name = streamName... case RPSampleBufferType.audioMic: options.constraints = FPWCSApi2MediaConstraints(audio: false, videoCapturer:if capturer(useMic); { try publishStream = session.createStream(options) FPWCSApi2.getAudioManager().getAudioModule().deliverRecordedData(sampleBuffer) } ... break try publishStream?.publish()... } } |
Известные ограничения
1. При захвате системного звука на устройстве не будет играть музыка из iTunes.
2. Если какое-либо запущенное приложение захватывает микрофон, приложение ScreenCapturerSwift получает тишину в sampleBuffer
как для микрофона, так и для системного звука. После того, как приложение, захватившее микрофон, освободит его, необходимо остановить публикацию экрана и начать ее заново, чтобы снова получать звук.