Пример iOS-приложения с плеером и стримером
Данное приложение может использоваться для публикации WebRTC-видеопотока и воспроизведения любого из следующих типов потоков с Web Call Server:
...
Слева отображается видео с камеры, справа воспроизводится другой поток.
Работа с кодом примера
Для разбора кода возьмем версию примера TwoWayStreaming, которая доступена доступна для скачивания в соответствующей сборке 2.5.2.
...
1. Импорт API. код
Code Block | ||||
---|---|---|---|---|
| ||||
#import <FPWCSApi2/FPWCSApi2.h> |
...
- URL WCS-сервера
- имя серверного приложения defaultApp
Code Block | ||||
---|---|---|---|---|
| ||||
- (FPWCSApi2Session *)connect { FPWCSApi2SessionOptions *options = [[FPWCSApi2SessionOptions alloc] init]; options.urlServer = _connectUrl.text; options.appKey = @"defaultApp"; NSError *error; FPWCSApi2Session *session = [FPWCSApi2 createSession:options error:&error]; if (!session) {... [session connect]; UIAlertController * alert = [UIAlertController alertControllerWithTitle:@"Failed to connect" message:error.localizedDescription preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction* okButton = [UIAlertAction actionWithTitle:@"Ok" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { [self onDisconnected]; }]; [alert addAction:okButton]; [self presentViewController:alert animated:YES completion:nil]; return nil; } [session on:kFPWCSSessionStatusEstablished callback:^(FPWCSApi2Session *rSession){ [self changeConnectionStatus:[rSession getStatus]]; [self onConnected:rSession]; }]; [session on:kFPWCSSessionStatusDisconnected callback:^(FPWCSApi2Session *rSession){ [self changeConnectionStatus:[rSession getStatus]]; [self onDisconnected]; }]; [session on:kFPWCSSessionStatusFailed callback:^(FPWCSApi2Session *rSession){ [self changeConnectionStatus:[rSession getStatus]]; [self onDisconnected]; }]; [session connect]; return session; } |
3. Публикация видеопотока.
FPWCSApi2Session createStream, FPWCSApi2Stream publish код
Методу createStream передаются параметры:
- имя публикуемого потока
- вид для локального отображения
- параметр record = true для записи потока при публикации
- размеры и FPS публикуемого видео при публикации с iPad
Code Block | ||||
---|---|---|---|---|
| ||||
- (FPWCSApi2Stream *)publishStream {
FPWCSApi2Session *session = [FPWCSApi2 getSessions][0];
FPWCSApi2StreamOptions *options = [[FPWCSApi2StreamOptions alloc] init];
options.name = _localStreamName.text;
options.display = _localDisplay;
if ( UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad ) {
options.constraints = [[FPWCSApi2MediaConstraints alloc] initWithAudio:YES videoWidth:640 videoHeight:480 videoFps:15];
}
NSError *error;
FPWCSApi2Stream *stream = [session createStream:options error:&error];
if (!stream) {
UIAlertController * alert = [UIAlertController
alertControllerWithTitle:@"Failed to publish"
message:error.localizedDescription
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* okButton = [UIAlertAction
actionWithTitle:@"Ok"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
[self onUnpublished];
}];
[alert addAction:okButton];
[self presentViewController:alert animated:YES completion:nil];
return nil;
}
[stream on:kFPWCSStreamStatusPublishing callback:^(FPWCSApi2Stream *rStream){
[self changeStreamStatus:rStream];
[self onPublishing:rStream];
}];
[stream on:kFPWCSStreamStatusUnpublished callback:^(FPWCSApi2Stream *rStream){
[self changeStreamStatus:rStream];
[self onUnpublished];
}];
[stream on:kFPWCSStreamStatusFailed callback:^(FPWCSApi2Stream *rStream){
[self changeStreamStatus:rStream];
[self onUnpublished];
}];
if(![stream 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 onUnpublished];
}];
[alert addAction:okButton];
[self presentViewController:alert animated:YES completion:nil];
}
return stream;
} |
4. Переключение камеры во время публикации потока
FPWCSApi2Stream switchCamera код
Code Block | ||||
---|---|---|---|---|
| ||||
- (void)switchCameraButton:(UIButton *)button {
if ([FPWCSApi2 getSessions].count) {
FPWCSApi2Session *session = [FPWCSApi2 getSessions][0];
NSArray *streams = [session getStreams];
for (FPWCSApi2Stream *stream in streams ) {
if ([stream isPublished]) {
NSLog(@"Found published stream, switching camera");
[stream switchCamera];
}
}
} else {
NSLog(@"No active sessions found");
}
} |
5. Воспроизведение видеопотока.
FPWCSApi2Session createStream, FPWCSApi2Stream play код
Методу createStream передаются параметры:
- имя воспроизводимого потока
- вид для отображения потока
Code Block | ||||
---|---|---|---|---|
| ||||
- (FPWCSApi2Stream *)playStream { FPWCSApi2Session *session = [FPWCSApi2 getSessions][0]; FPWCSApi2StreamOptions *options = [[FPWCSApi2StreamOptions alloc] init]; options.name = _remoteStreamName.text; options.display = _remoteDisplay; NSError *error; FPWCSApi2Stream *stream = [session createStream:options error:nil]; if (!stream) { UIAlertController * alert = [UIAlertController alertControllerWithTitle:@"Failed to play" message:error.localizedDescription preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction* okButton = [UIAlertAction return session; } |
3. Публикация видеопотока.
FPWCSApi2Session createStream, FPWCSApi2Stream publish код
Методу createStream передаются параметры:
- имя публикуемого потока
- вид для локального отображения
- параметр record = true для записи потока при публикации
- размеры и FPS публикуемого видео при публикации с iPad
Code Block | ||||
---|---|---|---|---|
| ||||
- (FPWCSApi2Stream *)publishStream { FPWCSApi2Session *session = [FPWCSApi2 getSessions][0]; FPWCSApi2StreamOptions *options = [[FPWCSApi2StreamOptions alloc] init]; options.name = _localStreamName.text; options.display = _localDisplay; if ( UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad ) { options.constraints = [[FPWCSApi2MediaConstraints alloc] initWithAudio:YES videoWidth:640 videoHeight:480 videoFps:15]; } NSError *error; FPWCSApi2Stream *stream = [session createStream:options error:&error]; ... if(![stream publish:&error]) { UIAlertController * alert = [UIAlertController alertControllerWithTitle:@"Failed to publish" actionWithTitle:@"Ok" message:error.localizedDescription style:UIAlertActionStyleDefault preferredStyle:UIAlertControllerStyleAlert]; handler:^(UIAlertAction* *okButton action)= {[UIAlertAction actionWithTitle:@"Ok" [self onStopped]; style:UIAlertActionStyleDefault }]; [alert addAction:okButton]; [self presentViewController:alert animated:YES completion:nil]; handler:^(UIAlertAction * action) { return nil; } [stream on:kFPWCSStreamStatusPlaying callback:^(FPWCSApi2Stream *rStream){ [self changeStreamStatus:rStream]; [self onPlaying:rStreamonUnpublished]; }]; [stream 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)[alert addAction:okButton]; [self changeStreamStatus:rStreampresentViewController:alert animated:YES completion:nil]; }]; return [stream on:kFPWCSStreamStatusStopped callback:^(FPWCSApi2Stream *rStream)stream; } |
4. Переключение камеры во время публикации потока
FPWCSApi2Stream switchCamera код
Code Block | ||||
---|---|---|---|---|
| ||||
- (void)switchCameraButton:(UIButton *)button { if ([selfFPWCSApi2 changeStreamStatus:rStream];getSessions].count) { [self onStopped]; }FPWCSApi2Session *session = [FPWCSApi2 getSessions][0]; [stream on:kFPWCSStreamStatusFailed callback:^(FPWCSApi2Stream *rStream){ NSArray *streams = [session getStreams]; [self changeStreamStatus:rStream]; for (FPWCSApi2Stream *stream in streams [self onStopped]; ) { }]; if (![stream play:&errorisPublished]) { UIAlertController * alert = [UIAlertController NSLog(@"Found published stream, switching camera"); [stream switchCamera]; } alertControllerWithTitle:@"Failed to play" } } else { NSLog(@"No active sessions found"); } } |
5. Воспроизведение видеопотока.
FPWCSApi2Session createStream, FPWCSApi2Stream play код
Методу createStream передаются параметры:
- имя воспроизводимого потока
- вид для отображения потока
Code Block | ||||
---|---|---|---|---|
| ||||
- (FPWCSApi2Stream *)playStream { FPWCSApi2Session *session = message:error.localizedDescription[FPWCSApi2 getSessions][0]; FPWCSApi2StreamOptions *options = [[FPWCSApi2StreamOptions alloc] init]; options.name = _remoteStreamName.text; options.display = _remoteDisplay; NSError *error; FPWCSApi2Stream *stream = [session createStream:options preferredStyleerror:UIAlertControllerStyleAlertnil]; ... if(![stream play:&error]) { UIAlertActionUIAlertController * okButtonalert = [UIAlertActionUIAlertController actionWithTitlealertControllerWithTitle:@"OkFailed to play" style:UIAlertActionStyleDefault message:error.localizedDescription handler:^(UIAlertAction * action) { preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction* okButton = [UIAlertAction actionWithTitle:@"Ok" }]; [alert addAction:okButton]; [self presentViewController:alert animated:YES completion:nil]; style:UIAlertActionStyleDefault } return stream; } |
6. Остановка воспроизведения видеопотока.
FPWCSApi2Stream stop код
Code Block | ||||
---|---|---|---|---|
| ||||
- (void)playButton:(UIButton *)button { [self changeViewState:button enabled:NO]; if ([button.titleLabel.text isEqualToString:@"STOP"]) { if ([FPWCSApi2 getSessions].counthandler:^(UIAlertAction * action) { FPWCSApi2Stream *stream; for (FPWCSApi2Stream *s in [[FPWCSApi2 getSessions][0] getStreams]) { if ([[s getName] isEqualToString:_remoteStreamName.text]) { stream = s}]; [alert addAction:okButton]; break; [self presentViewController:alert animated:YES completion:nil]; } return stream; } |
6. Остановка воспроизведения видеопотока.
FPWCSApi2Stream stop код
Code Block | ||||
---|---|---|---|---|
| ||||
- (void)playButton:(UIButton *)button }{ [self changeViewState:button enabled:NO]; if ([button.titleLabel.text } isEqualToString:@"STOP"]) { if (!stream[FPWCSApi2 getSessions].count) { NSLog(@"Stop playing, nothing to stop"); FPWCSApi2Stream *stream; for (FPWCSApi2Stream *s in [[FPWCSApi2 getSessions][self0] onStoppedgetStreams];) { return; if ([[s getName] isEqualToString:_remoteStreamName.text]) { } NSError *errorstream = s; [stream stop:&error]; } else { break; NSLog(@"Stop playing, no session");} [self onStopped];} } } elseif (!stream) { if ([FPWCSApi2 getSessions].count) { NSLog(@"Stop playing, nothing to stop"); [self changeViewState:_remoteStreamName enabled:NO]; [self playStreamonStopped]; } else { return; NSLog(@"Start playing, no session"); } [self onStopped]NSError *error; } } } |
7. Остановка публикации видеопотока. ViewController.m, line 274
FPWCSApi2Stream stop код
Code Block | ||||
---|---|---|---|---|
| ||||
- (void)publishButton:(UIButton *)button { [stream stop:&error]; } else { [self changeViewState:button enabled:NO]; if ([button.titleLabel.text isEqualToString:@"STOP"]) { NSLog(@"Stop playing, no session"); if ([FPWCSApi2self getSessions].count) {onStopped]; FPWCSApi2Stream *stream;} ... for (FPWCSApi2Stream *s in [[FPWCSApi2 getSessions][0] getStreams])} } |
7. Остановка публикации видеопотока.
FPWCSApi2Stream stop код
Code Block | ||||
---|---|---|---|---|
| ||||
- (void)publishButton:(UIButton *)button { [self changeViewState:button enabled:NO]; if ([[s getName] isEqualToString:_localStreamName.text]) { stream = s; button.titleLabel.text isEqualToString:@"STOP"]) { if ([FPWCSApi2 getSessions].count) { FPWCSApi2Stream break*stream; for (FPWCSApi2Stream *s in } [[FPWCSApi2 getSessions][0] getStreams]) { } if ([[s if (!streamgetName] isEqualToString:_localStreamName.text]) { NSLog(@"Stop publishing, nothing to stop") stream = s; [self onUnpublished] break; return;} } NSError *error; [stream stop:&error]; } else { if (!stream) { NSLog(@"Stop publishing, nonothing to sessionstop"); [self onUnpublished]; } } else {return; if ([FPWCSApi2 getSessions].count) {} [self changeViewState:_localStreamName enabled:NO]NSError *error; [self publishStreamstream stop:&error]; } else { NSLog(@"StartStop publishing, no session"); [self onUnpublished]; } }... } } |
8. Закрытие соединения.
FPWCSApi2Session disconnect код
Code Block | ||||
---|---|---|---|---|
| ||||
- (void)connectButton:(UIButton *)button { [self changeViewState:button enabled:NO]; if ([button.titleLabel.text isEqualToString:@"DISCONNECT"]) { if ([FPWCSApi2 getSessions].count) { FPWCSApi2Session *session = [FPWCSApi2 getSessions][0]; NSLog(@"Disconnect session with server %@", [session getServerUrl]); [session disconnect]; } else { NSLog(@"Nothing to disconnect"); [self onDisconnected]; } } else { //todo check url is not empty [self changeViewState:_connectUrl enabled:NO]; [self connect]; ... } } |