WebRTC音视频通话-RTC直播本地视频及相册视频文件

2023-10-26

WebRTC音视频通话-RTC直播本地视频及相册视频文件

WebRTC音视频通话-RTC直播本地视频文件效果图如下
在这里插入图片描述

WebRTC音视频通话-RTC直播本地视频文件时候,用到了AVPlayer、CADisplayLink。

一、通过AVPlayer播放本地视频

  • AVPlayer是什么?

AVPlayer是基于AVFoundation框架的一个类,很接近底层,灵活性强,可以自定义视频播放样式。

  • AVPlayerLayer是什么?

AVPlayerLayer是视频播放时候的画面展示层。

  • CADisplayLink是什么?

CADisplayLink和NSTimer一样,是一个定时器。但是CADisplayLink会和屏幕的刷新率始终保持一致(很多时候会使用CADisplayLink来检测屏幕的帧率)。

  • AVPlayerItemVideoOutput是什么?

AVPlayerItemVideoOutput是PlayerItem视频的输出,通过AVPlayerItemVideoOutput可以获取视频的CVPixelBufferRef

下面就实现本地视频播放过程中结合ossrs进行WebRTC直播。

AVPlayer设置播放本地视频

 AVPlayerItem *playerItem = [[AVPlayerItem alloc] initWithURL:[NSURL fileURLWithPath:videoPath]];
 (void)reloadPlayItem:(AVPlayerItem *)playerItem {
    self.playerItem = playerItem;
    [self initPlayerVideoOutput];
        
    self.player = [AVPlayer playerWithPlayerItem:self.playerItem];
}

开始配置播放展示AVPlayerLayer

- (void)startPlay {
    if (self.isPlaying) {
        return;
    }
    
    AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];
    self.playerLayer = playerLayer;
    self.playerLayer.backgroundColor = [UIColor clearColor].CGColor;
    self.playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
    
    [self.superView.layer addSublayer:self.playerLayer];
    self.playerLayer.frame = self.superView.bounds;

    [self.player seekToTime:CMTimeMake(0, 1)];
    [self.player play];
    [self startAnimating];
}

通过KVO监控播放器状态

/**
 *  通过KVO监控播放器状态
 */
- (void)observeValueForKeyPath:(NSString *)keyPath {
    DebugLog(@"observeValueForKeyPath:%@", keyPath);
    
    AVPlayerItem *videoItem = self.playerItem;
    if ([keyPath isEqualToString:@"timeControlStatus"]) {
        
        /**
         typedef NS_ENUM(NSInteger, AVPlayerTimeControlStatus) {
             AVPlayerTimeControlStatusPaused = 0,
             AVPlayerTimeControlStatusWaitingToPlayAtSpecifiedRate = 1,
             AVPlayerTimeControlStatusPlaying = 2
         } API_AVAILABLE(macos(10.12), ios(10.0), tvos(10.0), watchos(3.0));
         */
        // 监听播放器timeControlStatus 指示当前是否正在播放,无限期暂停播放,或在等待适当的网络条件时暂停播放
        if (@available(iOS 10.0, *)) {
            switch (self.player.timeControlStatus) {
                case AVPlayerTimeControlStatusPaused: {
                    NSLog(@"AVPlayerTimeControlStatusPaused");
                    // 暂停
                    self.isPlaying = NO;
                }
                    break;
                case AVPlayerTimeControlStatusWaitingToPlayAtSpecifiedRate: {
                    NSLog(@"AVPlayerTimeControlStatusWaitingToPlayAtSpecifiedRate");
                    // 等待
                }
                    break;
                case AVPlayerTimeControlStatusPlaying: {
                    NSLog(@"AVPlayerTimeControlStatusPlaying");
                    // 播放
                    self.isPlaying = YES;
                }
                    break;
                default:
                    break;
            }
        } else {
            // Fallback on earlier versions
        }
    }
}

设置关键的AVPlayerItemVideoOutput

- (void)initPlayerVideoOutput {
    //输出yuv 420格式
    NSDictionary *pixBuffAttributes = @{(id)kCVPixelBufferPixelFormatTypeKey: @(kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange)};
    AVPlayerItemVideoOutput *output = [[AVPlayerItemVideoOutput alloc] initWithPixelBufferAttributes:pixBuffAttributes];
    [self.playerItem addOutput:output];
    self.playerItemVideoOutput = output;
    
    [self.playerItemVideoOutput setDelegate:self queue:dispatch_get_main_queue()];
    
    // 如果将 AVPlayerItemVideoOutput 类的输出(对于 suppressesPlayerRendering 的值为 YES)添加到 AVPlayerItem,则该项目的视频媒体将不会由 AVPlayer 呈现,而音频媒体、字幕媒体和其他类型的媒体(如果存在) , 将被渲染。
    self.playerItemVideoOutput.suppressesPlayerRendering = NO;
}

之后通过displayLink开启实时调用AVPlayerItemVideoOutput得到视频画面CVPixelBufferRef

#pragma mark - DisplayLink
- (void)startDisplayLink {
    if (self.displayLink) {
        return;
    }
    self.displayLink = [CADisplayLink displayLinkWithTarget:[YYWeakProxy proxyWithTarget:self]
     selector:@selector(handleDisplayLink:)];
    [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    // self.displayLink.preferredFramesPerSecond = 2;
    self.displayLink.paused = NO;
}

- (void)handleDisplayLink:(CADisplayLink *)displayLink {
    //do something
    CMTime outputItemTime = kCMTimeInvalid;
    CFTimeInterval nextVSync = ([displayLink timestamp] + [displayLink duration]);
    outputItemTime = [[self playerItemVideoOutput] itemTimeForHostTime:nextVSync];
    if ([[self playerItemVideoOutput] hasNewPixelBufferForItemTime:outputItemTime]) {
        CVPixelBufferRef pixelBuffer = NULL;
        pixelBuffer = [[self playerItemVideoOutput] copyPixelBufferForItemTime:outputItemTime itemTimeForDisplay:NULL];
        // ..... do something with pixbuffer
        if (self.delegate && [self.delegate respondsToSelector:@selector(videoLivePlayerPixelBufferRef:)]) {
            [self.delegate videoLivePlayerPixelBufferRef:pixelBuffer];
        }
        
        if (pixelBuffer != NULL) {
            CFRelease(pixelBuffer);
        }
    }
}

- (void)stopDisplayLink {
    [self.displayLink invalidate];
    self.displayLink = nil;
}

这样在播放过程中,通过CADisplayLink获取到CVPixelBufferRef,之后使用WebRTC来进行直播。

完整播放视频过程中获得CVPixelBufferRef代码如下

SDVideoLivePlayer.h

#import <Foundation/Foundation.h>
#import <AudioToolbox/AudioToolbox.h>
#import <AVFoundation/AVFoundation.h>

@protocol SDVideoLivePlayerDelegate;
@interface SDVideoLivePlayer : NSObject

@property (nonatomic, strong) UIView *superView;

@property (nonatomic, weak) id<SDVideoLivePlayerDelegate> delegate;

- (instancetype)initWithSuperView:(UIView *)superView;

- (void)reloadPlayItem:(AVPlayerItem *)playerItem;

/// 开始播放
- (void)startPlay;

/// 结束播放
- (void)stopPlay;

@end

@protocol SDVideoLivePlayerDelegate <NSObject>

- (void)videoLivePlayerPixelBufferRef:(CVPixelBufferRef)pixelBufferRef;

@end

SDVideoLivePlayer.m

#import "SDVideoLivePlayer.h"

@interface SDVideoLivePlayer ()<AVPlayerItemOutputPullDelegate> {

}

@property (nonatomic, strong) AVPlayer *player;
@property (nonatomic, strong) AVPlayerLayer *playerLayer;
@property (nonatomic, strong) AVPlayerItem *playerItem;
@property (nonatomic, assign) BOOL isPlaying;
@property (nonatomic, strong) AVPlayerItemVideoOutput *playerItemVideoOutput;
@property (nonatomic, strong) CADisplayLink *displayLink;

@end

@implementation SDVideoLivePlayer

- (instancetype)initWithSuperView:(UIView *)superView
{
    self = [super init];
    if (self) {
        self.superView = superView;
        self.isPlaying = NO;
        [self addNotifications];
        [self initPlayerVideoOutput];
    }
    return self;
}

- (void)initPlayerVideoOutput {
    //输出yuv 420格式
    NSDictionary *pixBuffAttributes = @{(id)kCVPixelBufferPixelFormatTypeKey: @(kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange)};
    AVPlayerItemVideoOutput *output = [[AVPlayerItemVideoOutput alloc] initWithPixelBufferAttributes:pixBuffAttributes];
    [self.playerItem addOutput:output];
    self.playerItemVideoOutput = output;
    
    [self.playerItemVideoOutput setDelegate:self queue:dispatch_get_main_queue()];
    
    // 如果将 AVPlayerItemVideoOutput 类的输出(对于 suppressesPlayerRendering 的值为 YES)添加到 AVPlayerItem,则该项目的视频媒体将不会由 AVPlayer 呈现,而音频媒体、字幕媒体和其他类型的媒体(如果存在) , 将被渲染。
    self.playerItemVideoOutput.suppressesPlayerRendering = NO;
}

- (void)reloadPlayItem:(AVPlayerItem *)playerItem {
    
    self.playerItem = playerItem;
    [self initPlayerVideoOutput];
        
    self.player = [AVPlayer playerWithPlayerItem:self.playerItem];
}

- (void)startPlay {
    if (self.isPlaying) {
        return;
    }
    
    AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];
    self.playerLayer = playerLayer;
    self.playerLayer.backgroundColor = [UIColor clearColor].CGColor;
    self.playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
    
    [self.superView.layer addSublayer:self.playerLayer];
    self.playerLayer.frame = self.superView.bounds;

    [self.player seekToTime:CMTimeMake(0, 1)];
    [self.player play];
    [self startAnimating];
}

- (void)stopPlay {
    self.isPlaying = NO;
    [self.player pause];
    [self stopAnimating];
}

/**
 *  通过KVO监控播放器状态
 */
- (void)observeValueForKeyPath:(NSString *)keyPath {
    DebugLog(@"observeValueForKeyPath:%@", keyPath);
    
    AVPlayerItem *videoItem = self.playerItem;
    if ([keyPath isEqualToString:@"timeControlStatus"]) {
        
        /**
         typedef NS_ENUM(NSInteger, AVPlayerTimeControlStatus) {
             AVPlayerTimeControlStatusPaused = 0,
             AVPlayerTimeControlStatusWaitingToPlayAtSpecifiedRate = 1,
             AVPlayerTimeControlStatusPlaying = 2
         } API_AVAILABLE(macos(10.12), ios(10.0), tvos(10.0), watchos(3.0));
         */
        // 监听播放器timeControlStatus 指示当前是否正在播放,无限期暂停播放,或在等待适当的网络条件时暂停播放
        if (@available(iOS 10.0, *)) {
            switch (self.player.timeControlStatus) {
                case AVPlayerTimeControlStatusPaused: {
                    NSLog(@"AVPlayerTimeControlStatusPaused");
                    // 暂停
                    self.isPlaying = NO;
                }
                    break;
                case AVPlayerTimeControlStatusWaitingToPlayAtSpecifiedRate: {
                    NSLog(@"AVPlayerTimeControlStatusWaitingToPlayAtSpecifiedRate");
                    // 等待
                }
                    break;
                case AVPlayerTimeControlStatusPlaying: {
                    NSLog(@"AVPlayerTimeControlStatusPlaying");
                    // 播放
                    self.isPlaying = YES;
                }
                    break;
                default:
                    break;
            }
        } else {
            // Fallback on earlier versions
        }
    }
}

- (void)audioSessionInterrupted:(NSNotification *)notification{
    //通知类型
    NSDictionary * info = notification.userInfo;
    if ([[info objectForKey:AVAudioSessionInterruptionTypeKey] integerValue] == 1) {
        [self.player pause];
    } else {
        [self.player play];
    }
}

- (void)startAnimating {
    [self startDisplayLink];
    self.displayLink.paused = NO;
}

- (void)stopAnimating {
    self.displayLink.paused = YES;
    [self stopDisplayLink];
}

- (void)pauseAnimating {
    self.displayLink.paused = YES;
    [self stopDisplayLink];
}

- (void)resumeAnimating {
    if (!self.displayLink) {
        [self startDisplayLink];
    }
    self.displayLink.paused = NO;
}

#pragma mark - DisplayLink
- (void)startDisplayLink {
    if (self.displayLink) {
        return;
    }
    self.displayLink = [CADisplayLink displayLinkWithTarget:[YYWeakProxy proxyWithTarget:self]
     selector:@selector(handleDisplayLink:)];
    [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    // self.displayLink.preferredFramesPerSecond = 2;
    self.displayLink.paused = NO;
}

- (void)handleDisplayLink:(CADisplayLink *)displayLink {
    //do something
    CMTime outputItemTime = kCMTimeInvalid;
    CFTimeInterval nextVSync = ([displayLink timestamp] + [displayLink duration]);
    outputItemTime = [[self playerItemVideoOutput] itemTimeForHostTime:nextVSync];
    if ([[self playerItemVideoOutput] hasNewPixelBufferForItemTime:outputItemTime]) {
        CVPixelBufferRef pixelBuffer = NULL;
        pixelBuffer = [[self playerItemVideoOutput] copyPixelBufferForItemTime:outputItemTime itemTimeForDisplay:NULL];
        // ..... do something with pixbuffer
        if (self.delegate && [self.delegate respondsToSelector:@selector(videoLivePlayerPixelBufferRef:)]) {
            [self.delegate videoLivePlayerPixelBufferRef:pixelBuffer];
        }
        
        if (pixelBuffer != NULL) {
            CFRelease(pixelBuffer);
        }
    }
}

- (void)stopDisplayLink {
    [self.displayLink invalidate];
    self.displayLink = nil;
}

#pragma mark - AVPlayerItemOutputPullDelegate
- (void)outputMediaDataWillChange:(AVPlayerItemOutput *)sender {
    [self stopPlay];
}

#pragma mark - Observers
- (void)addNotifications {
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(replay:) name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
    
    // 音频播放被中断
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(audioSessionInterrupted:) name:AVAudioSessionInterruptionNotification object:nil];
    
    __weak typeof(self) weakSelf = self;
    if (@available(iOS 10.0, *)) {
        [self.KVOController observe:self.player keyPath:@"timeControlStatus" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew block:^(id  _Nullable observer, id  _Nonnull object, NSDictionary<NSString *,id> * _Nonnull change) {
            __strong typeof(weakSelf) strongSelf = weakSelf;
            [strongSelf observeValueForKeyPath:@"timeControlStatus"];
        }];
    }
}

- (void)removeNotifications {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    [self.KVOController unobserveAll];
}

- (void)replay:(NSNotification *)notification {
    if (notification.object == self.player.currentItem) {
        [self.player seekToTime:CMTimeMake(0, 1)];
        [self.player play];
    }
}

- (void)dealloc {
    [self removeNotifications];
}

@end

二、获取相册视频

获取相册视频代码如下,得到AVPlayerItem,调用- (void)reloadPlayItem:(AVPlayerItem *)playerItem;

- (void)startPlayAlbumVideo:(SDMediaModel *)mediaModel {
    if (!(mediaModel && mediaModel.phasset)) {
        return;
    }
    __weak typeof(self) weakSelf = self;
    [[PhotoKitManager shareInstance] requestPlayerItemForVideo:mediaModel.phasset completion:^(AVPlayerItem *playerItem) {
        __strong typeof(weakSelf) strongSelf = weakSelf;
        [strongSelf.videoLivePlayer reloadPlayItem:playerItem];
        [strongSelf.videoLivePlayer startPlay];
    } failure:^{
        __strong typeof(weakSelf) strongSelf = weakSelf;
    }];
}

三、WebRTC来进行RTC直播视频

CADisplayLink获取到CVPixelBufferRef,我们使用WebRTC来进行直播,相当于我们直播我们预先准备好的一个视频。
在之前实现了GPUImage视频通话视频美颜滤镜,这里用到了RTCVideoFrame,我们需要将得到的视频的CVPixelBufferRef,通过封装成RTCVideoFrame,通过调用RTCVideoSource的didCaptureVideoFrame方法实现最终的效果。

RTCVideoSource设置如下

- (RTCVideoTrack *)createVideoTrack {
    RTCVideoSource *videoSource = [self.factory videoSource];
    self.localVideoSource = videoSource;

    // 如果是模拟器
    if (TARGET_IPHONE_SIMULATOR) {
        if (@available(iOS 10, *)) {
            self.videoCapturer = [[RTCFileVideoCapturer alloc] initWithDelegate:self];
        } else {
            // Fallback on earlier versions
        }
    } else{
        self.videoCapturer = [[RTCCameraVideoCapturer alloc] initWithDelegate:self];
    }
    
    RTCVideoTrack *videoTrack = [self.factory videoTrackWithSource:videoSource trackId:@"video0"];
    
    return videoTrack;
}


- (void)createMediaSenders {
    if (!self.isPublish) {
        return;
    }
    
    NSString *streamId = @"stream";
    
    // Audio
    RTCAudioTrack *audioTrack = [self createAudioTrack];
    self.localAudioTrack = audioTrack;
    
    RTCRtpTransceiverInit *audioTrackTransceiver = [[RTCRtpTransceiverInit alloc] init];
    audioTrackTransceiver.direction = RTCRtpTransceiverDirectionSendOnly;
    audioTrackTransceiver.streamIds = @[streamId];
    
    [self.peerConnection addTransceiverWithTrack:audioTrack init:audioTrackTransceiver];
    
    // Video
    RTCVideoTrack *videoTrack = [self createVideoTrack];
    self.localVideoTrack = videoTrack;
    RTCRtpTransceiverInit *videoTrackTransceiver = [[RTCRtpTransceiverInit alloc] init];
    videoTrackTransceiver.direction = RTCRtpTransceiverDirectionSendOnly;
    videoTrackTransceiver.streamIds = @[streamId];
    [self.peerConnection addTransceiverWithTrack:videoTrack init:videoTrackTransceiver];
}

详细内容请看iOS端调用ossrs音视频通话部分。

将得到的视频的CVPixelBufferRef,通过封装成RTCVideoFrame,再通过调用RTCVideoSource的didCaptureVideoFrame方法。

- (RTCVideoFrame *)webRTCClient:(WebRTCClient *)client videoPixelBufferRef:(CVPixelBufferRef)videoPixelBufferRef {
    RTCCVPixelBuffer *rtcPixelBuffer =
    [[RTCCVPixelBuffer alloc] initWithPixelBuffer:videoPixelBufferRef];
    RTCCVPixelBuffer *rtcPixelBuffer = [[RTCCVPixelBuffer alloc] initWithPixelBuffer:pixelBuffer];
    int64_t timeStampNs = CMTimeGetSeconds(CMSampleBufferGetPresentationTimeStamp(sampleBuffer)) *
    1000000000;
    RTCVideoFrame *rtcVideoFrame = [[RTCVideoFrame alloc] initWithBuffer:rtcPixelBuffer                                   rotation:RTCVideoRotation_0                                      timeStampNs:timeStampNs];
                  
    return rtcVideoFrame;
}

使用RTCVideoSource调用didCaptureVideoFrame

[self.localVideoSource capturer:capturer didCaptureVideoFrame:frame];

其他
之前搭建ossrs服务,可以查看:https://blog.csdn.net/gloryFlow/article/details/132257196
之前实现iOS端调用ossrs音视频通话,可以查看:https://blog.csdn.net/gloryFlow/article/details/132262724
之前WebRTC音视频通话高分辨率不显示画面问题,可以查看:https://blog.csdn.net/gloryFlow/article/details/132262724
修改SDP中的码率Bitrate,可以查看:https://blog.csdn.net/gloryFlow/article/details/132263021
GPUImage视频通话视频美颜滤镜,可以查看:https://blog.csdn.net/gloryFlow/article/details/132265842

四、小结

WebRTC音视频通话-RTC直播本地视频文件。主要用到AVPlayer播放视频,AVPlayerItemVideoOutput得到CVPixelBufferRef,将处理后的CVPixelBufferRef生成RTCVideoFrame,通过调用WebRTC的localVideoSource中实现的didCaptureVideoFrame方法。内容较多,描述可能不准确,请见谅。

本文地址:https://blog.csdn.net/gloryFlow/article/details/132267068

学习记录,每天不停进步。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

WebRTC音视频通话-RTC直播本地视频及相册视频文件 的相关文章

  • 如何在 Android webRTC 上启用 H264

    如何在 Android WebRTC 上启用 H264 PeerConnection to createOfferSDP中没有h264描述 由于某种原因 Google 默认会阻止他们自己的软件编解码器 因此 如果您的芯片组中没有硬件解码 或
  • 该捆绑包在 Mac OS X 上无效

    我在将应用程序上传到应用程序商店时遇到问题 每次我将应用程序上传到苹果时 它都会显示 无效的二进制文件 因此我尝试在 Xcode Organizer 中验证存档的应用程序 但收到此错误 该捆绑包无效 Apple 目前不接受使用此版本 SDK
  • iOS CorePlot如何安装

    我正在尝试在我的一个 iOS 项目中使用 CorePlot 但即使按照说明并在网上查找后 我仍无法正确安装该框架 我觉得文档没有发展 不再描述安装框架的正确方法 我跟着这些说明 https code google com p core pl
  • clickedButtonAtIndex 方法未被调用

    当用户点击按钮时UIAlertView the clickedButtonAtIndex应该调用方法 但是 它没有 in the h我已经打电话给UIAlertView协议 interface RechercherViewControlle
  • iOS5 上的 UIImagePickerController 内存泄漏

    我在 iOS5 和 XCode4 2 上开发的应用程序中使用 UIImagePickerController 出现内存泄漏 我不知道为什么会出现这种泄漏 您能给我答案吗 和我的代码 void createImagePicker picker
  • 在 Objective-C 中从异步块增加变量

    我在 Objective C 中开发的服务遇到了一些难题 该服务的目的是解析核心数据实体列表并为每个对象下载相应的图像文件 该服务的原始设计因太多同时下载请求而阻塞了我的网络服务器 为了解决这个问题 我将负责执行下载请求的代码移至递归方法中
  • 为什么turn服务器不支持tcp连接?

    我是 WebRTC 新手 我需要为我的 webrtc 应用程序配置我自己的 Turn 服务器 我使用以下命令安装了我的转弯服务器 apt get install coturn 我只需要通过 tcp 运行转变服务器 它不必使用 UDP 进行任
  • 加速度计和校准 - iPhone SDK

    我需要在 iPhone 游戏中使用加速计的功能 我只需通过倾斜设备来移动图像即可 然而 YouTube 上的大多数视频仅显示以某种方式反转的倾斜功能 而忘记包含校准 我希望用户将他们的设备校准到他们所处的任何位置 有谁知道我应该如何开始 我
  • 将左按钮添加到 UINavigationBar (iPhone)

    我创建了一个新的基于导航的 iPhone 应用程序 我将其添加到 RootViewController 中 void viewDidLoad super viewDidLoad UIBarButtonItem addButton UIBar
  • 如何获取默认的iOS浏览器名称?

    Stack Overflow 应用程序检测到我越狱设置的默认浏览器 Chrome 的名称 我怎样才能在 Objective C 和 Swift 中实现同样的目标 只是名称 不是 ActivityView 代码 Example Update
  • setAnnotation - 无法识别的选择器

    点击搜索按钮后在设备上运行时 我收到此错误 但它在模拟器中工作 2013 03 08 17 58 33 981 IPAD 2661 907 Slider values are min 5000 000000 and max 500000 0
  • 如何删除 Objective-C 中的子视图?

    我已以编程方式将 UIButton 和 UITextView 作为子视图添加到我的视图中 notesDescriptionView UIView alloc initWithFrame CGRectMake 0 0 320 460 note
  • 如何从 CustomViewController 中更改 RootViewController(在 AppDelegate 中)?

    再会 我的应用程序具有在 AppDelegate 中加载的授权表单 SigninController 登录后 在 SigninController m 中检查 应该出现 TabBarController 作为应用程序的主视图 如何将控制器从
  • 从 Angularjs 应用程序中的 iOS Webview 调用 Javascript 函数

    我的问题是 当我尝试从 UIWebView 中调用 AngularJS 应用程序中存在的 javascript 函数时 该函数无法识别 当我在典型的 html 结构中调用该函数时 该函数会按预期被识别 下面提供的示例 目标 C void v
  • UIWebview 中的 NSString

    我有一个NSString和我的项目中的 webView iPhone 的 Objective C 我称之为index html在 webView 及其内部我插入了我的脚本 javascript 如何在脚本中将 NSString 作为 var
  • MediaStream 未处理的承诺拒绝:[object DOMError](在 Safari 11 中)

    在下面初始化 WebRTC 的方法中 我在 Safari Tech Preview 11 中遇到了未处理的承诺拒绝 具体来说 当我分配MediaStream像这样的视频元素 video srcObject event stream 堆栈跟踪
  • Xcode 不再识别测试

    我已经解决这个问题几天了 但我没有任何想法 我在 Xcode 中使用单元测试 效果很好 突然 Xcode 不再识别我的测试 如果我进入测试面板 它会显示我的测试为零 我其实有13个 运行单独测试或某些课程的能力现已消失 它不会在编辑器区域的
  • 如何在ios中重置触摸、触摸移动的NSTimer

    我正在开发一个应用程序 其中我必须在 3 秒后隐藏控制按钮 所以我使用 NSTimer 编写代码 然后开始触摸 它可以工作 但问题是 当我再次触摸任何其他按钮时 我的计时器不会重置 即使我像拖动一样移动我的触摸示例 如果我拖动或移动触摸 它
  • 在 Objective-C (iPhone) 中从 Excel 文件读取数据 [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我在 google 中搜索过 但似乎没有找到从 Objective C 读取 Excel 文件的方法 我找到的唯一答案是首先转换为 CSV
  • 如何捕获 Scrollview 内的缩放 UIImageView 进行裁剪?

    问题 缩小图像进行裁剪就可以了 放大图像后进行裁剪会显示应有的图像上方的图像 我在那里的 yOffset 是因为我想要的裁剪方块从滚动视图的下方开始 Code CGRect rect float yOffset 84 rect origin

随机推荐

  • 获得代理ippython_python爬虫之抓取代理服务器IP

    前言 使用爬虫爬取网站的信息常常会遇到的问题是 你的爬虫行为被对方识别了 对方把你的IP屏蔽了 返回不了正常的数据给你 那么这时候就需要使用代理服务器IP来伪装你的请求了 免费代理服务器网站有 下面我们以西刺免费代理IP为例子看看是如何获取
  • ArcGISMapsSDK for UnrealEngine_AQ

    ArcGISMapsSDK for UnrealEngine AQ Prepare 1 ArcGIS Maps SDK for game engines 2 ArcGIS Maps SDK for Unreal Engine Beta 2
  • jQuery 的 DOM 操作- 中

    文章目录 jQuery 的 DOM 操作 中 复制节点 复制节点介绍 复制节点 应用实例 替换节点 替换节点介绍 替换节点 应用实例 属性操作 样式操作 样式操作介绍 应用实例 jQuery 的 DOM 操作 中 注意本篇和jQuery 的
  • 【java】常用到的一些获取文件内容的方法

    一 前奏准备 获取文件名 根据文件名获取路径 文件路径名 String path public String getPath return path 根据路径获取文件名 return 文件名字符串 public String fileNam
  • Cocos 2dx iOS 平台初始化,OpenGL 初始化,分辨率设置

    Cocos 2dx iOS 平台初始化 OpenGL 初始化 分辨率设置 1 Main m int retVal UIApplicationMain argc argv nil AppController AppController mm
  • 判断操作系统和浏览器类型(苹果还是安卓,微信还是QQ)

    一 获取操作系统类型 function getOS var userAgent navigator in window userAgent in navigator navigator userAgent toLowerCase var v
  • FPGA时序约束学习笔记——IO约束(转)

    一 参考模型 图源来自 抢先版 小梅哥FPGA时序约束从遥望到领悟 二 参数分析 T 0 gt 3 Tclk1 T 3 gt 4 Tco T 4 gt 5 T 5 gt 6 Tdata T 4 gt 5 Tdata Init T 5 gt
  • 渗透测试流程

    文章目录 前言 一 渗透测试流程 二 流程步骤 1 明确目标 2 信息收集 3 漏洞探测 4 漏洞验证 5 提权所需 6 信息分析 7 信息整理 8 形成报告 总结 前言 渗透测试 出于保护信息系统的目的 更全面地找出测试对象的安全隐患 入
  • python数据库连接

    python数据库连接 import os import time import pymysql import sys class Myclass object def init self try self db pymysql conne
  • Springboot整合Activiti详解

    文章目录 版本依赖 配置文件 需要注意的问题 画流程图 activiti服务类进行编写 流程部署 流程定义 启动流程 流程实例 测试流程 启动流程 完成任务 受理任务 版本依赖 开发工具 IDEA SpringBoot 2 4 5 这里我试
  • MySQL

    1 MySQL概述 1 什么是数据库 数据库是一个存储数据的仓库 2 都有哪些公司在用数据库 金融机构 游戏网站 购物网站 论坛网站 3 提供数据库服务的软件 1 软件分类 MySQL SQL Server Oracle Mariadb D
  • 初中计算机试题戏曲进校园,【校园通讯】“戏曲进校园”走进东街学校,春风化新雨,戏曲百媚生!...

    原标题 校园通讯 戏曲进校园 走进东街学校 春风化新雨 戏曲百媚生 戏曲进校园 戏曲进校园 走进东街学校 春风化新雨 戏曲百媚生 文 东街学校 张永慰 弘扬民族文化 展现戏曲精华 10月10日 戏曲进校园 活动走进济水东街学校 为全体师生带
  • 3.18飞书面试(58min)

    3 18飞书面试 58min 1 问项目 首先是问redis是怎么用的 mq的消费是怎么写的呢 mq如何保证消息消费的可靠性 你在项目中用到了本地缓存 放在了业务代码内存中 那如果签到一半你的项目突然崩了 本地缓存都消失了 那不是会出问题啊
  • [人工智能-深度学习-66]:架构 - 人工智能的学习误区与正确思路、方法

    作者主页 文火冰糖的硅基工坊 文火冰糖 王文兵 的博客 文火冰糖的硅基工坊 CSDN博客 本文网址 https blog csdn net HiWangWenBing article details 122116482 目录 前言 第1章
  • spring+struts+ibatis

    原来的系统里面只采用了struts的框架 并且没有使用struts的校验功能 为方便开发 修改框架为spring struts ibatis组合1 添加需要的jar文件2 添加spring配置文件applicationContext xml
  • view-source是一种协议,查看源码

    view source是一种协议 早期基本上每个浏览器都支持这个协议 后来Microsoft考虑安全性 对于WindowsXP pack2以及更高版本以后IE就不再支持此协议 但是这个方法在FireFox和Chrome浏览器都还可以使用 如
  • Linux驱动_spi驱动(ICM20608)

    参考 Linux SPI 驱动分析 1 结构框架 StephenZhou CSDN博客 linux spi驱动 Linux SPI 驱动分析 2 框架层源码分析 StephenZhou CSDN博客 spi message init SPI
  • sql server 加密_列级SQL Server加密

    列加密 创建一个新的数据库并创建CustomerInfo表 CREATE DATABASE CustomerData Go USE CustomerData GO CREATE TABLE CustomerData dbo Customer
  • 机器学习/深度学习--手写数字识别(MNIST数据集)

    import torch 导入torchvision的transform模块 用来处理数据集 from torchvision import transforms from torchvision import datasets from
  • WebRTC音视频通话-RTC直播本地视频及相册视频文件

    WebRTC音视频通话 RTC直播本地视频及相册视频文件 WebRTC音视频通话 RTC直播本地视频文件效果图如下 WebRTC音视频通话 RTC直播本地视频文件时候 用到了AVPlayer CADisplayLink 一 通过AVPlay