检测用户何时开始录制视频

4

我有一个UIImagePickerController,用于录制视频。现在我想检测用户何时点击录制按钮。但是该委托并没有提供任何此类回调。

有什么好方法可以找出视频正在录制时吗?

4个回答

6

你说得对:代理并不会接收有关视频捕获何时发生的任何信息。简单的解决方案是自己编写相机控件,将 showsCameraControls 属性设置为 NO 并在自定义视图中提供自己的控件,使用 cameraOverlayView 属性,并让你的“捕获”按钮调用图像选择器控制器的 -startVideoCapture-stopVideoCapture 方法,同时提供任何你需要的通知给应用程序的其他部分。


下一个屏幕呢?您如何创建那些屏幕以进行视频回放和重拍? - PoolHallJunkie

2
在完美的世界中,你希望苹果提供几个代理来实现这一点。例如:

  • (void)imagePickerControllerDidStartVideoCapturing:(UIImagePickerController *)picker
  • (void)imagePickerControllerDidStopVideoCapturing:(UIImagePickerController *)picker

然而,现实情况(根据苹果文档)是:

  • UIImagePickerController协议太基础,无法做到这一点
  • 该类旨在直接使用,不支持子类化
  • 该类的视图层次结构是私有的,不能修改

文档还指出:"您可以将自定义视图分配给cameraOverlayView属性,并使用该视图呈现其他信息或管理相机接口与您的代码之间的交互"。

在我的应用程序中,我需要展示"UIProgressView"以指示视频还能录制多长时间。为了实现这一点,我需要能够检测视频捕获开始的时刻。

我不想禁用本地相机控件,因为它们很酷,而且我很懒,不想花费太多时间重新发明轮子。简单来说,我只需要捕捉一个大红色按钮被点击以开始或停止录制的事实。

我的解决方案是 "覆盖" 原始的开始/停止录制按钮,并使用以下方法为该视图启用用户交互:

overlayView = [[UIView alloc] initWithFrame:self.view.frame];

// Start/ Stop fake button
UIView *ssView = [[UIView alloc] initWithFrame:CGRectMake(self.view.frame.size.width / 2 - 35, self.view.frame.size.height - 71, 70, 70)];
[ssView setUserInteractionEnabled:YES];

// Background color below is only there to make sure my pseudo-button overlaps native Start/Stop button. Comment out the line below to leave it transparent
[ssView setBackgroundColor:[UIColor colorWithRed:0 green:1 blue:0 alpha:0.5f]];

UITapGestureRecognizer *t = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapped:)];
[ssView addGestureRecognizer:t];

[overlayView addSubview:ssView];  

// My own progress bar
UIProgressView *p = [[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleDefault];
[p setTintColor:[UIColor redColor]];
[p setCenter:CGPointMake(30, 130)];
[p setTransform:CGAffineTransformMakeRotation( M_PI / 2 )];
[p setProgress:0];

[overlayView addSubview:p];

pickerController.cameraOverlayView = overlayView;

我随后定义了一个轻触的事件处理程序,如下所示:

-(void)tapped:(id)sender {

    if (isRecording) {
        [pickerController stopVideoCapture];
        NSLog(@"Video capturing stopped...");
        // add your business logic here ie stop updating progress bar etc...
        [pickerController.cameraOverlayView setHidden:YES];
        isRecording = NO;
        return;
    }

    if ([pickerController startVideoCapture]) {
        NSLog(@"Video capturing started...");
        // add your business logic here ie start updating progress bar etc...
        isRecording = YES;
    }

}

接口文件的完整代码:

#import <UIKit/UIKit.h>
#import <MobileCoreServices/MobileCoreServices.h>

@interface ViewController : UIViewController <UIImagePickerControllerDelegate>
- (IBAction)openCamera:(id)sender;

@end

实现文件:

#import "ViewController.h"

@interface ViewController () {
    UIImagePickerController *pickerController;
    UIView* overlayView;
    BOOL isRecording;
}

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    isRecording = NO;

    pickerController = [[UIImagePickerController alloc] init];
    pickerController.delegate = self;
    pickerController.allowsEditing = NO;
    pickerController.videoMaximumDuration = 30.0f;
    pickerController.sourceType = UIImagePickerControllerSourceTypeCamera;
    pickerController.mediaTypes = [[NSArray alloc] initWithObjects: (NSString *) kUTTypeMovie, nil];

    // I want default controls be available here...
    pickerController.showsCameraControls = YES;

    overlayView = [[UIView alloc] initWithFrame:self.view.frame];

    // Start/ Stop fake button
    UIView *ssView = [[UIView alloc] initWithFrame:CGRectMake(self.view.frame.size.width / 2 - 35, self.view.frame.size.height - 71, 70, 70)];
    [ssView setUserInteractionEnabled:YES];
    // Background color below is only there to make sure myt pseudo-button overlaps native Start/Stop button
    [ssView setBackgroundColor:[UIColor colorWithRed:0 green:1 blue:0 alpha:0.5f]];

   UITapGestureRecognizer *t = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapped:)];
   [ssView addGestureRecognizer:t];

   [overlayView addSubview:ssView];

   // My own progress bar
   UIProgressView *p = [[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleDefault];
   [p setTintColor:[UIColor redColor]];
   [p setCenter:CGPointMake(30, 130)];
   [p setTransform:CGAffineTransformMakeRotation( M_PI / 2 )];
   [p setProgress:0];

   [overlayView addSubview:p];

   pickerController.cameraOverlayView = overlayView;

}

- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker {
    // Cancel button tapped
    [picker dismissViewControllerAnimated:YES completion:nil];
}

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {

    NSLog(@"Got image : %@", info);
    [picker dismissViewControllerAnimated:YES completion:nil];

    // Do something with video captured
}

-(void)tapped:(id)sender {

    if (isRecording) {
        [pickerController stopVideoCapture];
        NSLog(@"Video capturing stopped...");
        // add your business logic here ie stop updating progress bar etc...
        [pickerController.cameraOverlayView setHidden:YES];
        isRecording = NO;
        return;
    }

    if ([pickerController startVideoCapture]) {
        NSLog(@"Video capturing started...");
        // add your business logic here ie start updating progress bar etc...
        isRecording = YES;
    }

}

- (IBAction)openCamera:(id)sender {
    [pickerController.cameraOverlayView setHidden:NO];
    [self presentViewController:pickerController animated:YES completion:nil];    
}
@end

您可能已经注意到,一旦视频捕获停止,我就会隐藏cameraOverlayView。
[pickerController.cameraOverlayView setHidden:YES];

这是为了让“重新拍摄/播放和使用”原生控件在视频录制后正常工作。

太棒了,你有按钮的图片吗? - Sasho

1
这是一个Swift 3的UIImagePickerController类,只有在相机处于横屏模式时才允许用户捕捉视频。
当您创建UIImagePickerController时:
var imagePicker = CameraVideo_ViewController()

你还需要在代理中添加这个:

func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
    self.imagePicker.isInFinalScreen = false
}

这是一个类。我没有时间整理它,而且它非常混乱…我尝试了所有可能的方法来创建这个类,而不删除默认控件。使用了许多技巧,因此在删除变量时要非常小心,因为大多数变量即使看起来无用也很重要。如果有人能重新发布一个更干净、更适合新手的版本,我/我们将不胜感激。

import UIKit
import AVFoundation;

class CameraVideo_ViewController: UIImagePickerController {

var viewMain:UIView!
var lastOrientationWasLandscape:UIDeviceOrientation?
var isForLibrary:Bool! = false
var parentController:UIViewController!

override func viewDidAppear(_ animated: Bool) {
    if isForLibrary == true {
        return
    }
    let orientation = UIDevice.current.orientation
    if orientation == .landscapeLeft || orientation == .landscapeRight {
        lastOrientationWasLandscape = orientation
    }
    if (self.isInFinalScreen == true){
        self.recordBut.frame = CGRect(x: self.view.frame.size.width - 70 - 16, y: self.view.frame.size.height / 2 - 35, width: 70, height: 70)
        self.isInFinalScreen = false
    }
    recordBut.alpha = 1
    recordBut.isUserInteractionEnabled = true
    retakeBut.alpha = 1
    retakeBut.isUserInteractionEnabled = true
    UIDevice.current.beginGeneratingDeviceOrientationNotifications()
    NotificationCenter.default.addObserver(self, selector: #selector(orientationChanged(_:)), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)
    let notif = Notification.init(name: NSNotification.Name.UIDeviceOrientationDidChange)
    orientationChanged(notif)
}
override func viewWillDisappear(_ animated: Bool) {
    viewMain.alpha = 0
    viewMain.isUserInteractionEnabled = false
    lastOrientationWasLandscape = nil
    recordBut.alpha = 0
    recordBut.isUserInteractionEnabled = false
    retakeBut.alpha = 0
    retakeBut.isUserInteractionEnabled = false
    self.viewMain.alpha = 0
    self.viewMain.isUserInteractionEnabled = false
    isInFinalScreenBool = false
    recordedThisSession = false
    if isForLibrary == true {
        return
    }
    print("hidingCameraView")
    NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)
    UIDevice.current.endGeneratingDeviceOrientationNotifications()

}

override func viewDidLoad() {
    super.viewDidLoad()
    if isForLibrary == true {
        return
    }

    viewMain = UIView()
    viewMain.frame = CGRect(x: view.frame.minX, y: view.frame.minY, width: view.frame.width, height: view.frame.height)
    viewMain.backgroundColor = UIColor.clear

    let viewBg = UIView()
    viewBg.frame = CGRect(x: view.frame.minX, y: view.frame.minY, width: view.frame.width, height: view.frame.height)
    viewBg.backgroundColor = UIColor.black
    viewBg.alpha = 0.5

    let viewAlertBg = UIView()
    viewAlertBg.frame = CGRect(x: view.frame.width/2 - 250/2, y: view.frame.height/2 - 100/2 - 25, width: 250, height: 100)
    viewAlertBg.backgroundColor = UIColor.white
    viewAlertBg.alpha = 1
    viewAlertBg.clipsToBounds = true;
    var path = UIBezierPath(roundedRect:viewAlertBg.bounds,
                            byRoundingCorners:[.topLeft, .topRight],
                            cornerRadii: CGSize(width: 25, height:  25))

    var maskLayer = CAShapeLayer()

    maskLayer.path = path.cgPath
    viewAlertBg.layer.mask = maskLayer


    let viewAlertBgUnderline = UIView()
    viewAlertBgUnderline.frame = CGRect(x: view.frame.width/2 - 250/2, y: viewAlertBg.frame.maxY, width: 250, height: 1)
    viewAlertBgUnderline.backgroundColor = UIColor.lightGray
    viewAlertBgUnderline.alpha = 1
    viewAlertBgUnderline.clipsToBounds = true;

    let viewAlertCancelBut = UIButton()
    viewAlertCancelBut.frame = CGRect(x: view.frame.width/2 - 250/2, y: viewAlertBgUnderline.frame.maxY, width: 250, height: 50)
    viewAlertCancelBut.backgroundColor = UIColor.white
    viewAlertCancelBut.setTitle("Back", for: .normal)
    viewAlertCancelBut.setTitleColor(UIColor.blue, for: .normal)
    path = UIBezierPath(roundedRect:viewAlertCancelBut.bounds,
                            byRoundingCorners:[.bottomLeft, .bottomRight],
                            cornerRadii: CGSize(width: 25, height:  25))

    maskLayer = CAShapeLayer()

    maskLayer.path = path.cgPath
    viewAlertCancelBut.layer.mask = maskLayer
    viewAlertCancelBut.addTarget(self, action: #selector(onBack(_:)), for: .touchUpInside)

    let alertLabel = UILabel()
    alertLabel.numberOfLines = 4
    alertLabel.frame = CGRect(x: 16, y: 16, width: 250 - 32, height: 100 - 32)
    alertLabel.textAlignment = NSTextAlignment.center
    alertLabel.adjustsFontSizeToFitWidth = true
    alertLabel.font = UIFont.systemFont(ofSize: 12)
    alertLabel.backgroundColor = UIColor.clear
    alertLabel.textColor = UIColor.black
    let boldText = "The video must be recorded in landscape mode.\n"
    let normalText  = "Please hold your device in a horizontal position!"
    let attrs = [NSFontAttributeName : UIFont.boldSystemFont(ofSize: 13)]
    let boldString = NSMutableAttributedString(string:boldText, attributes:attrs)
    let attributedString = NSMutableAttributedString(string:"")
    attributedString.append(boldString)
    attributedString.append(NSMutableAttributedString(string:normalText, attributes:nil))
    alertLabel.attributedText = attributedString

    viewAlertBg.addSubview(alertLabel)
    viewMain.addSubview(viewBg)
    viewMain.addSubview(viewAlertCancelBut)
    viewMain.addSubview(viewAlertBg)
    viewMain.addSubview(viewAlertBgUnderline)

    viewMain.alpha = 0
    viewMain.isUserInteractionEnabled = false

    // Start/ Stop fake button
    recordBut = UIButton()
    if (UIDevice.current.userInterfaceIdiom == .phone){
        recordBut.frame = CGRect(x: self.view.frame.size.width / 2 - 35, y: self.view.frame.size.height - 70 - 2, width: 70, height: 70)
    }else{
        recordBut.frame = CGRect(x: self.view.frame.size.height - 70 - 16, y: self.view.frame.size.width/2 - 35, width: 70, height: 70)
    }

    recordBut.setTitle("", for: .normal)
    recordBut.setTitleColor(UIColor.blue, for: .normal)
    recordBut.isUserInteractionEnabled = true
    recordBut.backgroundColor = UIColor(red: 0, green: 1, blue: 0, alpha: 0.5)
    recordBut.addTarget(self, action: #selector(tapped(_:)), for: .touchUpInside)
    self.view.addSubview(recordBut)

    retakeBut = UIButton()
    if (UIDevice.current.userInterfaceIdiom == .phone){
        retakeBut.frame = CGRect(x: 0, y: self.view.frame.size.height - 70, width: 80, height: 70)
    }else{
        retakeBut.frame = CGRect(x: 0, y: self.view.frame.size.width - 70, width: 80, height: 70)
    }
    retakeBut.setTitle("", for: .normal)
    retakeBut.setTitleColor(UIColor.blue, for: .normal)
    retakeBut.isUserInteractionEnabled = true
    retakeBut.backgroundColor = UIColor(red: 0, green: 1, blue: 0, alpha: 0.5)
    retakeBut.addTarget(self, action: #selector(retake(_:)), for: .touchUpInside)
    self.view.addSubview(retakeBut)
    self.view.addSubview(viewMain)

}
override func viewDidLayoutSubviews() {
    if isForLibrary == true {
        return
    }
    self.adjustViews(for: UIDevice.current.orientation)
}
var t:UITapGestureRecognizer!
var recordBut:UIButton!
var retakeBut:UIButton!
var isInFinalScreen:Bool  = false
var isRecording:Bool = false
var isInFinalScreenBool:Bool = false
var recordedThisSession:Bool = false
func tapped(_ sender:UIButton){
    if (isRecording == false && self.startVideoCapture()){
        recordedThisSession = true
        isRecording = true
        retakeBut.alpha = 0
        retakeBut.isUserInteractionEnabled = false
    }else{
        retakeBut.alpha = 1
        retakeBut.isUserInteractionEnabled = true
        recordBut.alpha = 0
        recordBut.isUserInteractionEnabled = false
        self.stopVideoCapture()
        isRecording = false
        if (UIDevice.current.orientation != .portrait){
            self.adjustViews(for: UIDevice.current.orientation)
        }
        isInFinalScreen = true
        return
    }
}
func retake(_ sender:UIButton){
    if (recordedThisSession == false){
        onBack(sender)
    }
    self.dismiss(animated: true, completion: {
        self.parentController.present((self.parentController as! AddVideo_ViewController).imagePicker, animated: true, completion: {

        })
    })
}

func onBack(_ sender:UIButton){
    self.isInFinalScreen = false
    self.dismiss(animated: true, completion: {
    })
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}

func orientationChanged(_ notification: Notification) {
    self.adjustViews(for: UIDevice.current.orientation)
}

func adjustViews(for orient: UIDeviceOrientation) {
    var orientation = orient
    if (orientation.isLandscape == true) || (orientation.isFlat && lastOrientationWasLandscape?.isPortrait == false) {
        print(".....landscape.....")
            if (UIDevice.current.userInterfaceIdiom == .pad){
                self.recordBut.frame = CGRect(x: self.view.frame.size.width - 70 - 16, y: self.view.frame.size.height/2 - 35, width: 70, height: 70)
                self.retakeBut.frame = CGRect(x: 0, y: self.view.frame.size.height - 70, width: 80, height: 70)
            }else{
                recordBut.frame = CGRect(x: self.view.frame.size.width / 2 - 35, y: self.view.frame.size.height - 70 - 2, width: 70, height: 70)
                retakeBut.frame = CGRect(x: 0, y: self.view.frame.size.height - 70, width: 80, height: 70)
            }
            if (recordedThisSession == false){
                if (UIDevice.current.userInterfaceIdiom == .pad){
                    self.retakeBut.frame = CGRect(x: self.view.frame.size.width - 100, y: self.view.frame.size.height - 70, width: 100, height: 70)
                }else{
                    retakeBut.frame = CGRect(x: 0, y: self.view.frame.size.height - 70, width: 80, height: 70)
                }
            }

            if (self.isInFinalScreenBool == true){
                return
            }
            if (self.isInFinalScreen == true){
                self.isInFinalScreenBool = !isInFinalScreenBool
                self.isInFinalScreen = false
                return
            }
            if (self.isRecording){
                return
            }
            self.viewMain.alpha = 0
            self.viewMain.isUserInteractionEnabled = false
            self.lastOrientationWasLandscape = orientation
    }
    else {
        print(".....portrait.....")
        self.lastOrientationWasLandscape = UIDeviceOrientation(rawValue: UIDeviceOrientation.portrait.rawValue)
            if (UIDevice.current.userInterfaceIdiom == .pad){
                self.recordBut.frame = CGRect(x: self.view.frame.size.width - 70 - 16, y: self.view.frame.size.height / 2 - 35, width: 70, height: 70)
                self.retakeBut.frame = CGRect(x: 0, y: self.view.frame.size.height - 70, width: 80, height: 70)
            }else{
                recordBut.frame = CGRect(x: self.view.frame.size.width / 2 - 35, y: self.view.frame.size.height - 70 - 2, width: 70, height: 70)
                retakeBut.frame = CGRect(x: 0, y: self.view.frame.size.height - 70, width: 80, height: 70)
            }
            if (recordedThisSession == false){
                if (UIDevice.current.userInterfaceIdiom == .pad){
                    self.retakeBut.frame = CGRect(x: self.view.frame.size.width - 100, y: self.view.frame.size.height - 70, width: 100, height: 70)
                }else{
                    retakeBut.frame = CGRect(x: 0, y: self.view.frame.size.height - 70, width: 80, height: 70)
                }
            }

            if (self.isInFinalScreenBool == true){
                return
            }
            if (self.isInFinalScreen){
                return
            }
            if (self.isRecording){
                return
            }
            self.viewMain.alpha = 1
            self.viewMain.isUserInteractionEnabled = true
    }

}
/*
// MARK: - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    // Get the new view controller using segue.destinationViewController.
    // Pass the selected object to the new view controller.
}
*/

}

祝你好运!


0
根据iOS API,方法“startVideoCapture”返回一个布尔值。 表示正在录制。 表示以下情况之一:
  1. 电影捕获已经在进行中。
  2. 设备不支持电影捕获。
  3. 设备的磁盘空间不足。
[参考:http://developer.apple.com/library/ios/#documentation/uikit/reference/UIImagePickerController_Class/UIImagePickerController/UIImagePickerController.html]
只要“startRecording”返回一个“是”,你几乎可以确定它正在录制。当然,为了检查这一点,你总是可以手动定义自己的回调函数与NSTimer(尽管以前有一些关于其准确性的争论)。

但是您注意到调用此 API 将启动录制吗? - Bastian

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接