Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Пример iOS-приложения для управления медиа-устройствами

Данный пример может использоваться как стример для публикации WebRTC-видеопотока с Web Call Server и позволяет выбрать медиа-устройства и следующие параметры для публикуемого и проигрываемого видео

...

На скриншоте ниже представлен пример во время публикации потока.
В URL в поле ввода 192.168.2.107 - адрес WCS-сервера.
Слева отображается видео с камеры, справа воспроизводится опубликованный поток.
Вид с контролами для настроек публикации показывается при нажатии на кнопку 'Local settings', а вид с контролами для настроек воспроизведения - при нажатии на кнопку 'Remote settings'.

Работа с кодом примера

Для разбора кода возьмем версию примера MediaDevices, которая доступена доступна для скачивания в соответствующей сборке 2.5.2.

...

1. Импорт API. код

Code Block
languagebashcpp
themeRDark
#import <FPWCSApi2/FPWCSApi2.h>

...

FPWCSApi2 getMediaDevices код

Code Block
languagebashcpp
themeRDark
localDevices = [FPWCSApi2 getMediaDevices];

...

FPWCSApi2MediaDeviceList.audio[0] код

Code Block
languagebashcpp
themeRDark
_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
languagebashcpp
themeRDark
_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
languagebashcpp
themeRDark
- (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;
    }
    if ([_sendVideo.control isOn]) {
        FPWCSApi2VideoConstraints *video = [[FPWCSApi2VideoConstraints alloc] init];
        for (FPWCSApi2MediaDevice *device in localDevices.video) {...
        NSArray *res =  if ([device.label isEqualToString:_camSelector_videoResolutionSelector.input.text]) { componentsSeparatedByString:@"x"];
        video.minWidth = video.maxWidth = [res[0] integerValue];
   video.deviceID = device.deviceID;
   video.minHeight = video.maxHeight =      }[res[1] integerValue];
        }
video.minFrameRate        NSArray *res = video.maxFrameRate = [_videoResolutionSelectorfpsSelector.input.text componentsSeparatedByString:@"x"integerValue];
        video.minWidthbitrate = video.maxWidth = [res[0] [_videoBitrate.input.text integerValue];
        ret.video.minHeight = video.maxHeight = [res[1] integerValue];
    }
     video.minFrameRate = video.maxFrameRate = [_fpsSelector.input.text integerValue];
  return ret;
}


5. Определение параметров для проигрываемого потока.

FPWCSApi2MediaConstraints.audio, FPWCSApi2MediaConstraints.video код

Code Block
languagecpp
themeRDark
- (FPWCSApi2MediaConstraints *)toMediaConstraints {
    FPWCSApi2MediaConstraints  video.bitrate = [_videoBitrate.input.text integerValue];*ret = [[FPWCSApi2MediaConstraints alloc] init];
    ret.audio = [[FPWCSApi2AudioConstraints alloc] init];
    if ([_playVideo.control isOn]) {
        ret.FPWCSApi2VideoConstraints *video = video [[FPWCSApi2VideoConstraints alloc] init];
    }
    return ret;
}

5. Определение параметров для проигрываемого потока.

FPWCSApi2MediaConstraints.audio, FPWCSApi2MediaConstraints.video код

Code Block
languagebash
themeRDark
- (FPWCSApi2MediaConstraints *)toMediaConstraints {
    FPWCSApi2MediaConstraints *ret = [[FPWCSApi2MediaConstraints alloc] init];
    ret.audio = [[FPWCSApi2AudioConstraints alloc] init];
    if ([_playVideo.control isOn]) {video.minWidth = video.maxWidth = [_videoResolution.width.text integerValue];
        video.minHeight = video.maxHeight = [_videoResolution.height.text integerValue];
        FPWCSApi2VideoConstraints *video.bitrate = [[FPWCSApi2VideoConstraints alloc] init]_bitrate.input.text integerValue];
        video.minWidth = video.maxWidthquality = [_videoResolutionquality.widthinput.text integerValue];
        ret.video.minHeight = video.maxHeight = [_videoResolution.height.text integerValue];
        video.bitrate = [_bitrate.input.text integerValue];
        video.quality = [_quality.input.text integerValue];
        ret.video = video;
    }
    return ret;
}

6. Локальное тестирование микрофона и камеры

FPWCSApi2 getMediaAccess, AVAudioRecorder record, AVAudioRecorder stop код

Code Block
languagebash
themeRDark
- (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:&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
languagebash
themeRDark
- (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
languagebash
themeRDark
- (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];;
    }
    return ret;
}


6. Локальное тестирование микрофона и камеры

FPWCSApi2 getMediaAccess, AVAudioRecorder record, AVAudioRecorder stop код

Code Block
languagecpp
themeRDark
- (void)testButton:(UIButton *)button {
    if ([button.titleLabel.text isEqualToString:@"Test"]) {
        [self onStopped]NSError *error;
    }];
    
    [FPWCSApi2 getMediaAccess:[_localStream on:kFPWCSStreamStatusFailed callback:^(FPWCSApi2Stream *stream){
        [self changeStreamStatus:streamlocalControl toMediaConstraints] display:_videoView.local error:&error];
        [self onStopped_testButton setTitle:@"Release" forState:UIControlStateNormal];
    }];
    if(![_localStream publish:&error]) {

        [[AVAudioSession sharedInstance] UIAlertController * alert = [UIAlertController
setCategory:AVAudioSessionCategoryRecord error:&error];
        
        NSURL *url = [NSURL fileURLWithPath:@"/dev/null"];
        
        NSDictionary *settings = [NSDictionary alertControllerWithTitle:@"Failed to publish"dictionaryWithObjectsAndKeys:
                                  [NSNumber   message:error.localizedDescription
numberWithFloat: 44100.0],                     AVSampleRateKey,
                preferredStyle:UIAlertControllerStyleAlert];
        
        UIAlertAction* okButton = [UIAlertAction
NSNumber numberWithInt: kAudioFormatAppleLossless], AVFormatIDKey,
                                actionWithTitle:@"Ok"
          [NSNumber numberWithInt: 1],                         style:UIAlertActionStyleDefaultAVNumberOfChannelsKey,
                                  [NSNumber handler:^(UIAlertAction * action) {
numberWithInt: AVAudioQualityMax],         AVEncoderAudioQualityKey,
                                 [self onStoppednil];
        
        _recorder = [[AVAudioRecorder alloc] initWithURL:url settings:settings error:&error];
             }[_recorder prepareToRecord];
        _recorder.meteringEnabled = YES;
        [alert addAction:okButton_recorder record];
        _levelTimer = [selfNSTimer presentViewControllerscheduledTimerWithTimeInterval:alert animated:YES completion:nil];
    }
}

9. Воспроизведение видеопотока после публикации

FPWCSApi2Session createStream, FPWCSApi2Stream play код

Методу createStream передаются параметры:

  • имя воспроизводимого потока
  • вид для отображения потока
  • параметры видео и аудио
Code Block
languagebash
themeRDark
- (void)startPlaying 0.3 target: self selector: @selector(levelTimerCallback:) userInfo: nil repeats: YES];
    } else {
    FPWCSApi2StreamOptions  *options = [[FPWCSApi2StreamOptions alloc] init]FPWCSApi2 releaseLocalMedia:_videoView.local];
    options.name   = [_localStream getName];testButton setTitle:@"Test" forState:UIControlStateNormal];
        
    options.display   = _videoView.remote[_levelTimer invalidate];
    options.constraints   = [_remoteControlrecorder toMediaConstraintsstop];

    }
}


7. Создание сессии и подключение к серверу.

FPWCSApi2 createSession, FPWCSApi2Session connect код

В параметрах сессии указываются:

  • URL WCS-сервера
  • имя серверного приложения defaultApp
Code Block
languagecpp
themeRDark
-  NSError *error;
(void)start {
    if (!_remoteStreamsession =|| [_session createStream:options error:&error];
    if (!_remoteStreamgetStatus] != kFPWCSSessionStatusEstablished || ![[_session getServerUrl] isEqualToString:_urlInput.text]) {
        UIAlertController...
 * alert = [UIAlertController
    FPWCSApi2SessionOptions *options = [[FPWCSApi2SessionOptions alloc] init];
        options.urlServer = _urlInput.text;
        options.appKey = @"defaultApp";
        alertControllerWithTitle:@"Failed to play"
NSError *error;
        _session = [FPWCSApi2 createSession:options error:&error];
        ...
        [_session connect];
    } else {
   message:error.localizedDescription
     [self startStreaming];
    }
}


8. Публикация потока.

FPWCSApi2Session createStream, FPWCSApi2Stream publish код

Методу createStream передаются параметры:

  • имя публикуемого потока
  • вид для локального отображения
  • параметры аудио и видео
Code Block
languagecpp
themeRDark
- (void)startStreaming {
    FPWCSApi2StreamOptions *options = [[FPWCSApi2StreamOptions alloc] init];
    options.name =    [self getStreamName];
    options.display   preferredStyle:UIAlertControllerStyleAlert]= _videoView.local;
    options.constraints =   [_localControl toMediaConstraints];
    NSError *error;
   UIAlertAction* okButton_localStream = [UIAlertAction
  _session createStream:options error:&error];
    ...
    if(![_localStream publish:&error]) {
        UIAlertController *    alert = [UIAlertController
        actionWithTitle:@"Ok"
                             alertControllerWithTitle:@"Failed to publish"
    style:UIAlertActionStyleDefault
                                 message:error.localizedDescription
  handler:^(UIAlertAction * action) {
                                preferredStyle:UIAlertControllerStyleAlert];
       [self onStopped];
        UIAlertAction* okButton = [UIAlertAction
                        }];
        
   actionWithTitle:@"Ok"
     [alert addAction:okButton];
          [self presentViewController:alert animated:YES completion:nil];
        return;
    }
    [_remoteStream onstyle:kFPWCSStreamStatusPlaying callback:^(FPWCSApi2Stream *stream){
UIAlertActionStyleDefault
          [self changeStreamStatus:stream];
        [self onStarted];
        _useLoudSpeaker.control.userInteractionEnabled = YES;
     handler:^(UIAlertAction * action) {
    }];
    
    [_remoteStream on:kFPWCSStreamStatusNotEnoughtBandwidth callback:^(FPWCSApi2Stream *rStream){
        NSLog(@"Not enough bandwidth stream %@, consider using lower video resolution or bitrate. Bandwidth %ld bitrate %ld", [rStreamself getName], [rStream getNetworkBandwidth] / 1000, [rStream getRemoteBitrate] / 1000);
onStopped];
                   [self changeStreamStatus:rStream];
    }];
    
    [_remoteStream on:kFPWCSStreamStatusStopped callback:^(FPWCSApi2Stream *rStream){
  }];
       [self changeStreamStatus:rStream];
        [_localStreamalert stopaddAction:nilokButton];
        _useLoudSpeaker.control.userInteractionEnabled = NO;

[self presentViewController:alert animated:YES completion:nil];
    }];
    [_remoteStream on:kFPWCSStreamStatusFailed callback:^(FPWCSApi2Stream *rStream)
}


9. Воспроизведение видеопотока после публикации

FPWCSApi2Session createStream, FPWCSApi2Stream play код

Методу createStream передаются параметры:

  • имя воспроизводимого потока
  • вид для отображения потока
  • параметры видео и аудио
Code Block
languagecpp
themeRDark
- (void)startPlaying {
    FPWCSApi2StreamOptions *options =  [self changeStreamStatus:rStream[[FPWCSApi2StreamOptions alloc] init];
        if (_localStream &&options.name = [_localStream getStatus] == kFPWCSStreamStatusPublishing) {getName];
    options.display = _videoView.remote;
    options.constraints = [_localStreamremoteControl stop:niltoMediaConstraints];
        }NSError *error;
    _remoteStream =   _useLoudSpeaker.control.userInteractionEnabled = NO;

[_session createStream:options error:&error];
    }];...
    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];
    }

}

...

FPWCSApi2Stream muteAudio, unmuteAudio, muteVideo, unmuteVideo код

Code Block
languagebashcpp
themeRDark
- (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.muteVideo.control) {
        if (_localStream) {
            if (_localControl.muteVideo.control.isOn) {
                [_localStream muteVideo];
            } else {
                [_localStream unmuteVideo];
            }
        }
    }
}

...