...
1. Импорт API. код
Code Block | ||||
---|---|---|---|---|
| ||||
#import <FPWCSApi2/FPWCSApi2.h> |
...
FPWCSApi2 getMediaDevices код
Code Block | ||||
---|---|---|---|---|
| ||||
localDevices = [FPWCSApi2 getMediaDevices]; |
...
FPWCSApi2MediaDeviceList.audio[0] код
Code Block | ||||
---|---|---|---|---|
| ||||
_micSelector = [[WCSPickerInputView alloc] initWithLabelText:@"Mic" pickerDelegate:self];
//set default mic
if (localDevices.audio.count > 0) {
_micSelector.input.text = ((FPWCSApi2MediaDevice *)(localDevices.audio[0])).label;
} |
...
FPWCSApi2MediaDeviceList.video[0] код
Code Block | ||||
---|---|---|---|---|
| ||||
_camSelector = [[WCSPickerInputView alloc] initWithLabelText:@"Cam" pickerDelegate:self]; //set default cam if (localDevices.video.count > 0) { _camSelector.input.text = ((FPWCSApi2MediaDevice *)(localDevices.video[0])).label; } |
...
FPWCSApi2MediaConstraints.audio, FPWCSApi2MediaConstraints.video код
Code Block | ||||
---|---|---|---|---|
| ||||
- (FPWCSApi2MediaConstraints *)toMediaConstraints { FPWCSApi2MediaConstraints *ret = [[FPWCSApi2MediaConstraints alloc] init]; if ([_sendAudio.control isOn]) { FPWCSApi2AudioConstraints *audio = [[FPWCSApi2AudioConstraints alloc] init]; audio.useFEC = [_useFEC.control isOn]; audio.useStereo = [_useStereo.control isOn]; audio.bitrate = [_audioBitrate.input.text integerValue]; ret.audio = audio; } ret.audio = audio; } if ([_sendVideo.control isOn]) { FPWCSApi2VideoConstraints *video = [[FPWCSApi2VideoConstraints alloc] init]; for (FPWCSApi2MediaDevice *device in localDevices.video) { if ([device.label isEqualToString:_camSelector.input.text]) { video.deviceID = device.deviceID; } } NSArray *res = [ } } NSArray *res = [_videoResolutionSelector.input.text componentsSeparatedByString:@"x"]; video.minWidth = video.maxWidth = [res[0] integerValue]; video.minHeight = video.maxHeight = [res[1] integerValue]; video.minFrameRate = video.maxFrameRate = [_fpsSelector.input.text integerValue]; video.bitrate = [_videoBitrate.input.text integerValue]; ret.video = video; } ret.video = video; } return ret; } |
5. Определение параметров для проигрываемого потока.
FPWCSApi2MediaConstraints.audio, FPWCSApi2MediaConstraints.video код
Code Block | ||||
---|---|---|---|---|
| ||||
- (FPWCSApi2MediaConstraints *)toMediaConstraints { FPWCSApi2MediaConstraints *ret = [[FPWCSApi2MediaConstraints alloc] init]; ret.audio = [[FPWCSApi2AudioConstraints alloc] init]; if ([_playVideo.control isOn]) { FPWCSApi2VideoConstraints *video = [[FPWCSApi2VideoConstraints alloc] init]; video.minWidth = video.maxWidth = [_videoResolution.width.text integerValue]; video.minHeight = video.maxHeight = [_videoResolution.height.text integerValue]; video.bitrate = [_bitrate.input.text integerValue]; video.quality = [ video.quality = [_quality.input.text integerValue]; ret.video = video; } return ret; } |
6. Локальное тестирование микрофона и камеры
FPWCSApi2 getMediaAccess, AVAudioRecorder record, AVAudioRecorder stop код
Code Block | ||||
---|---|---|---|---|
| ||||
- (void)testButton:(UIButton *)button { if ([button.titleLabel.text isEqualToString:@"Test"]) { NSError *error; [FPWCSApi2 getMediaAccess:[_localControl toMediaConstraints] display:_videoView.local error:&error]; [_testButton setTitle:@"Release" forState:UIControlStateNormal]; [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryRecord error: [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryRecord error:&error]; NSURL *url = [NSURL fileURLWithPath:@"/dev/null"]; NSDictionary *settings = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithFloat: 44100.0], AVSampleRateKey, [NSNumber numberWithInt: kAudioFormatAppleLossless], AVFormatIDKey, [NSNumber numberWithInt: 1], AVNumberOfChannelsKey, [NSNumber numberWithInt: AVAudioQualityMax], AVEncoderAudioQualityKey, nil]; _recorder = [[AVAudioRecorder alloc] initWithURL:url settings:settings error:&error]; [_recorder prepareToRecord]; _recorder.meteringEnabled = YES; [_recorder record]; _levelTimer = [NSTimer scheduledTimerWithTimeInterval: 0.3 target: self selector: @selector(levelTimerCallback:) userInfo: nil repeats: YES]; } else { [FPWCSApi2 releaseLocalMedia:_videoView.local]; [_testButton setTitle:@"Test" forState:UIControlStateNormal]; [_levelTimer invalidate]; [_recorder stop]; } } |
7. Создание сессии и подключение к серверу.
FPWCSApi2 createSession, FPWCSApi2Session connect код
В параметрах сессии указываются:
- URL WCS-сервера
- имя серверного приложения defaultApp
Code Block | ||
---|---|---|
| ||
- (void)start {
if (!_session || [_session getStatus] != kFPWCSSessionStatusEstablished || ![[_session getServerUrl] isEqualToString:_urlInput.text]) {
if (_session && ![[_session getServerUrl] isEqualToString:_urlInput.text]) {
[_session on:kFPWCSSessionStatusDisconnected callback:^(FPWCSApi2Session *session){}];
[_session on:kFPWCSSessionStatusFailed callback:^(FPWCSApi2Session *session){}];
[_session disconnect];
}
FPWCSApi2SessionOptions *options = [[FPWCSApi2SessionOptions alloc] init];
options.urlServer = _urlInput.text;
options.appKey = @"defaultApp";
NSError *error;
_session = [FPWCSApi2 createSession:options error:&error];
if (!_session) {
UIAlertController * alert = [UIAlertController
alertControllerWithTitle:@"Failed to connect"
message:error.localizedDescription
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* okButton = [UIAlertAction
actionWithTitle:@"Ok"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
[self onStopped];
}];
[alert addAction:okButton];
[self presentViewController:alert animated:YES completion:nil];
return;
}
[_session on:kFPWCSSessionStatusEstablished callback:^(FPWCSApi2Session *session){
[self changeConnectionStatus:[session getStatus]];
[self startStreaming];
}];
[_session on:kFPWCSSessionStatusDisconnected callback:^(FPWCSApi2Session *session){
[self changeConnectionStatus:[session getStatus]];
[self onStopped];
}];
[_session on:kFPWCSSessionStatusFailed callback:^(FPWCSApi2Session *session){
[self changeConnectionStatus:[session getStatus]];
[self onStopped];
}];
[_session connect];
} else {
[self startStreaming];
}
} |
8. Публикация потока.
FPWCSApi2Session createStream, FPWCSApi2Stream publish код
Методу createStream передаются параметры:
- имя публикуемого потока
- вид для локального отображения
- параметры аудио и видео
Code Block | ||
---|---|---|
| ||
- (void)startStreaming {
FPWCSApi2StreamOptions *options = [[FPWCSApi2StreamOptions alloc] init];
options.name = [self getStreamName];
options.display = _videoView.local;
options.constraints = [_localControl toMediaConstraints];
NSError *error;
_localStream = [_session createStream:options error:&error];
if (!_localStream) {
UIAlertController * alert = [UIAlertController
alertControllerWithTitle:@"Failed to publish"
message:error.localizedDescription
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* okButton = [UIAlertAction
actionWithTitle:@"Ok"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
[self onStopped];
}];
[alert addAction:okButton];
[self presentViewController:alert animated:YES completion:nil];
return;
}
//initial mute
if (_localControl.muteAudio.control.isOn) {
[_localStream muteAudio];
}
if (_localControl.muteVideo.control.isOn) {
[_localStream muteVideo];
}
if (_lockCameraOrientation.control.isOn) {
[_localStream lockCameraOrientation];
}
[_localStream on:kFPWCSStreamStatusPublishing callback:^(FPWCSApi2Stream *stream){
[self changeStreamStatus:stream];
[self startPlaying];
}];
[_localStream on:kFPWCSStreamStatusUnpublished callback:^(FPWCSApi2Stream *stream){
[self changeStreamStatus:stream];
[self onStopped];
}];
[_localStream on:kFPWCSStreamStatusFailed callback:^(FPWCSApi2Stream *stream){
[self changeStreamStatus:stream];
[self onStopped];
}];
if(![_localStream publish:&error]) {
UIAlertController * alert = [UIAlertController
alertControllerWithTitle:@"Failed to publish"
message:error.localizedDescription
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* okButton = [UIAlertAction
actionWithTitle:@"Ok"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
[self onStopped];
}];
[alert addAction:okButton];
[self presentViewController:alert animated:YES completion:nil];
}
} |
9. Воспроизведение видеопотока после публикации
FPWCSApi2Session createStream, FPWCSApi2Stream play код
Методу createStream передаются параметры:
- имя воспроизводимого потока
- вид для отображения потока
- параметры видео и аудио
Code Block | ||
---|---|---|
| ||
- (void)startPlaying {
FPWCSApi2StreamOptions *options = [[FPWCSApi2StreamOptions alloc] init];
options.name = [_localStream getName];
options.display = _videoView.remote;
options.constraints = [_remoteControl toMediaConstraints];
NSError *error;
_remoteStream = [_session createStream:options error:&error];
if (!_remoteStream) {
UIAlertController * alert = [UIAlertController
alertControllerWithTitle:@"Failed to play"
message:error.localizedDescription
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* okButton = [UIAlertAction
actionWithTitle:@"Ok"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
[self onStopped];
}];
[alert addAction:okButton];
[self presentViewController:alert animated:YES completion:nil];
return;
}
[_remoteStream on:kFPWCSStreamStatusPlaying callback:^(FPWCSApi2Stream *stream){
[self changeStreamStatus:stream];
[self onStarted];
_useLoudSpeaker.control.userInteractionEnabled = YES;
}];
[_remoteStream on:kFPWCSStreamStatusNotEnoughtBandwidth callback:^(FPWCSApi2Stream *rStream){
NSLog(@"Not enough bandwidth stream %@, consider using lower video resolution or bitrate. Bandwidth %ld bitrate %ld", [rStream getName], [rStream getNetworkBandwidth] / 1000, [rStream getRemoteBitrate] / 1000);
[self changeStreamStatus:rStream];
}];
[_remoteStream on:kFPWCSStreamStatusStopped callback:^(FPWCSApi2Stream *rStream){
[self changeStreamStatus:rStream];
[_localStream stop:nil];
_useLoudSpeaker.control.userInteractionEnabled = NO;
}];
[_remoteStream on:kFPWCSStreamStatusFailed callback:^(FPWCSApi2Stream *rStream){
[self changeStreamStatus:rStream];
if (_localStream && [_localStream getStatus] == kFPWCSStreamStatusPublishing) {
[_localStream stop:nil];
}
_useLoudSpeaker.control.userInteractionEnabled = NO;
}];
if(![_remoteStream play:&error]) {
UIAlertController * alert = [UIAlertController
alertControllerWithTitle:@"Failed to play"
message:error.localizedDescription
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* okButton = [UIAlertAction
actionWithTitle:@"Ok"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
if (_localStream && [_localStream getStatus] == kFPWCSStreamStatusPublishing) {
[_localStream stop:nil];
}
}];
[alert addAction:okButton];
[self presentViewController:alert animated:YES completion:nil];
}
} |
10. Включение/выключение аудио и видео.
FPWCSApi2Stream muteAudio, unmuteAudio, muteVideo, unmuteVideo код
Code Block | ||
---|---|---|
| ||
- (void)controlValueChanged:(id)sender { [NSNumber numberWithFloat: 44100.0], AVSampleRateKey, [NSNumber numberWithInt: kAudioFormatAppleLossless], AVFormatIDKey, [NSNumber numberWithInt: 1], AVNumberOfChannelsKey, [NSNumber numberWithInt: AVAudioQualityMax], AVEncoderAudioQualityKey, nil]; _recorder = [[AVAudioRecorder alloc] initWithURL:url settings:settings error:&error]; [_recorder prepareToRecord]; _recorder.meteringEnabled = YES; [_recorder record]; _levelTimer = [NSTimer scheduledTimerWithTimeInterval: 0.3 target: self selector: @selector(levelTimerCallback:) userInfo: nil repeats: YES]; } else { [FPWCSApi2 releaseLocalMedia:_videoView.local]; [_testButton setTitle:@"Test" forState:UIControlStateNormal]; [_levelTimer invalidate]; [_recorder stop]; } } |
7. Создание сессии и подключение к серверу.
FPWCSApi2 createSession, FPWCSApi2Session connect код
В параметрах сессии указываются:
- URL WCS-сервера
- имя серверного приложения defaultApp
Code Block | ||||
---|---|---|---|---|
| ||||
- (void)start {
if (!_session || [_session getStatus] != kFPWCSSessionStatusEstablished || ![[_session getServerUrl] isEqualToString:_urlInput.text]) {
if (_session && ![[_session getServerUrl] isEqualToString:_urlInput.text]) {
[_session on:kFPWCSSessionStatusDisconnected callback:^(FPWCSApi2Session *session){}];
[_session on:kFPWCSSessionStatusFailed callback:^(FPWCSApi2Session *session){}];
[_session disconnect];
}
FPWCSApi2SessionOptions *options = [[FPWCSApi2SessionOptions alloc] init];
options.urlServer = _urlInput.text;
options.appKey = @"defaultApp";
NSError *error;
_session = [FPWCSApi2 createSession:options error:&error];
if (!_session) {
UIAlertController * alert = [UIAlertController
alertControllerWithTitle:@"Failed to connect"
message:error.localizedDescription
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* okButton = [UIAlertAction
actionWithTitle:@"Ok"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
[self onStopped];
}];
[alert addAction:okButton];
[self presentViewController:alert animated:YES completion:nil];
return;
}
[_session on:kFPWCSSessionStatusEstablished callback:^(FPWCSApi2Session *session){
[self changeConnectionStatus:[session getStatus]];
[self startStreaming];
}];
[_session on:kFPWCSSessionStatusDisconnected callback:^(FPWCSApi2Session *session){
[self changeConnectionStatus:[session getStatus]];
[self onStopped];
}];
[_session on:kFPWCSSessionStatusFailed callback:^(FPWCSApi2Session *session){
[self changeConnectionStatus:[session getStatus]];
[self onStopped];
}];
[_session connect];
} else {
[self startStreaming];
}
} |
8. Публикация потока.
FPWCSApi2Session createStream, FPWCSApi2Stream publish код
Методу createStream передаются параметры:
- имя публикуемого потока
- вид для локального отображения
- параметры аудио и видео
Code Block | ||||
---|---|---|---|---|
| ||||
- (void)startStreaming {
FPWCSApi2StreamOptions *options = [[FPWCSApi2StreamOptions alloc] init];
options.name = [self getStreamName];
options.display = _videoView.local;
options.constraints = [_localControl toMediaConstraints];
NSError *error;
_localStream = [_session createStream:options error:&error];
if (!_localStream) {
UIAlertController * alert = [UIAlertController
alertControllerWithTitle:@"Failed to publish"
message:error.localizedDescription
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* okButton = [UIAlertAction
actionWithTitle:@"Ok"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
[self onStopped];
}];
[alert addAction:okButton];
[self presentViewController:alert animated:YES completion:nil];
return;
}
//initial mute
if (_localControl.muteAudio.control.isOn) {
[_localStream muteAudio];
}
if (_localControl.muteVideo.control.isOn) {
[_localStream muteVideo];
}
if (_lockCameraOrientation.control.isOn) {
[_localStream lockCameraOrientation];
}
[_localStream on:kFPWCSStreamStatusPublishing callback:^(FPWCSApi2Stream *stream){
[self changeStreamStatus:stream];
[self startPlaying];
}];
[_localStream on:kFPWCSStreamStatusUnpublished callback:^(FPWCSApi2Stream *stream){
[self changeStreamStatus:stream];
[self onStopped];
}];
[_localStream on:kFPWCSStreamStatusFailed callback:^(FPWCSApi2Stream *stream){
[self changeStreamStatus:stream];
[self onStopped];
}];
if(![_localStream publish:&error]) {
UIAlertController * alert = [UIAlertController
alertControllerWithTitle:@"Failed to publish"
message:error.localizedDescription
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* okButton = [UIAlertAction
actionWithTitle:@"Ok"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
[self onStopped];
}];
[alert addAction:okButton];
[self presentViewController:alert animated:YES completion:nil];
}
} |
9. Воспроизведение видеопотока после публикации
FPWCSApi2Session createStream, FPWCSApi2Stream play код
Методу createStream передаются параметры:
- имя воспроизводимого потока
- вид для отображения потока
- параметры видео и аудио
Code Block | ||||
---|---|---|---|---|
| ||||
- (void)startPlaying {
FPWCSApi2StreamOptions *options = [[FPWCSApi2StreamOptions alloc] init];
options.name = [_localStream getName];
options.display = _videoView.remote;
options.constraints = [_remoteControl toMediaConstraints];
NSError *error;
_remoteStream = [_session createStream:options error:&error];
if (!_remoteStream) {
UIAlertController * alert = [UIAlertController
alertControllerWithTitle:@"Failed to play"
message:error.localizedDescription
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* okButton = [UIAlertAction
actionWithTitle:@"Ok"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
[self onStopped];
}];
[alert addAction:okButton];
[self presentViewController:alert animated:YES completion:nil];
return;
}
[_remoteStream on:kFPWCSStreamStatusPlaying callback:^(FPWCSApi2Stream *stream){
[self changeStreamStatus:stream];
[self onStarted];
_useLoudSpeaker.control.userInteractionEnabled = YES;
}];
[_remoteStream on:kFPWCSStreamStatusNotEnoughtBandwidth callback:^(FPWCSApi2Stream *rStream){
NSLog(@"Not enough bandwidth stream %@, consider using lower video resolution or bitrate. Bandwidth %ld bitrate %ld", [rStream getName], [rStream getNetworkBandwidth] / 1000, [rStream getRemoteBitrate] / 1000);
[self changeStreamStatus:rStream];
}];
[_remoteStream on:kFPWCSStreamStatusStopped callback:^(FPWCSApi2Stream *rStream){
[self changeStreamStatus:rStream];
[_localStream stop:nil];
_useLoudSpeaker.control.userInteractionEnabled = NO;
}];
[_remoteStream on:kFPWCSStreamStatusFailed callback:^(FPWCSApi2Stream *rStream){
[self changeStreamStatus:rStream];
if (_localStream && [_localStream getStatus] == kFPWCSStreamStatusPublishing) {
[_localStream stop:nil];
}
_useLoudSpeaker.control.userInteractionEnabled = NO;
}];
if(![_remoteStream play:&error]) {
UIAlertController * alert = [UIAlertController
alertControllerWithTitle:@"Failed to play"
message:error.localizedDescription
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* okButton = [UIAlertAction
actionWithTitle:@"Ok"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
if (_localStream && [_localStream getStatus] == kFPWCSStreamStatusPublishing) {
[_localStream stop:nil];
}
}];
[alert addAction:okButton];
[self presentViewController:alert animated:YES completion:nil];
}
} |
10. Включение/выключение аудио и видео.
FPWCSApi2Stream muteAudio, unmuteAudio, muteVideo, unmuteVideo код
Code Block | ||||
---|---|---|---|---|
| ||||
- (void)controlValueChanged:(id)sender { if (sender == _localControl.muteAudio.control) { if (_localStream) { if (_localControl.muteAudio.control.isOn) { [_localStream muteAudio]; } else { [_localStream unmuteAudio]; } } } else if (sender == _localControl.muteAudiomuteVideo.control) { if (_localStream) { if (_localControl.muteAudiomuteVideo.control.isOn) { [_localStream muteAudio]; muteVideo]; } else { [_localStream unmuteAudiounmuteVideo]; } } } else if (sender == _localControl.muteVideo.control) { if (_localStream) { if (_localControl.muteVideo.control.isOn) { [_localStream muteVideo]; } else { [_localStream unmuteVideo]; } } } } } } |
11. Остановка воспроизведения потока.
FPWCSApi2Stream stop код
Code Block | ||||
---|---|---|---|---|
| ||||
- (void)startButton:(UIButton *)button { button.userInteractionEnabled = NO; button.alpha = 0.5; _urlInput.userInteractionEnabled = NO; if ([button.titleLabel.text isEqualToString:@"Stop"]) { if (_remoteStream) { NSError *error; *error; [_remoteStream stop:&error]; } else { NSLog(@"No remote stream, failed to stop"); } } else { //start [self start]; } } |
12. Остановка публикации потока.
FPWCSApi2Stream stop код
Code Block | ||||
---|---|---|---|---|
| ||||
[_remoteStream on:kFPWCSStreamStatusStopped callback:^(FPWCSApi2Stream *rStream){ [self changeStreamStatus:rStream]; [_localStream stop:nil]; _useLoudSpeaker.control.userInteractionEnabled = NO; }]; |