开源 Objective-C IOS 应用开发(十九)视频的播放

文章的目的为了记录使用Objective-C 进行IOS app 开发学习的经历。本职为嵌入式软件开发,公司安排开发app,临时学习,完成app的开发。开发流程和要点有些记忆模糊,赶紧记录,防止忘记。

相关链接:

开源 Objective-C IOS 应用开发(一)macOS 的使用

开源 Objective-C IOS 应用开发(二)Xcode安装

开源 Objective-C IOS 应用开发(三)第一个iPhone的APP

开源 Objective-C IOS 应用开发(四)Xcode工程文件结构

开源 Objective-C IOS 应用开发(五)iOS操作(action)和输出口(Outlet)

开源 Objective-C IOS 应用开发(六)Objective-C 和 C语言

开源 Objective-C IOS 应用开发(七)Objective-C核心代码示例

开源 Objective-C IOS 应用开发(八)常见控件UI

开源 Objective-C IOS 应用开发(九)复杂控件-tableview

开源 Objective-C IOS 应用开发(十)数据持久化--文件

开源 Objective-C IOS 应用开发(十一)数据持久化--sqlite

开源 Objective-C IOS 应用开发(十二)通讯--ble

开源 Objective-C IOS 应用开发(十三)通讯--Http访问

开源 Objective-C IOS 应用开发(十四)传感器--陀螺仪和gps

开源 Objective-C IOS 应用开发(十五)通讯--蓝牙ble扫描

开源 Objective-C IOS 应用开发(十六)Storyboard模式下的纯代码界面

开源 Objective-C IOS 应用开发(十七)CAF音频的录制

开源 Objective-C IOS 应用开发(十八)音频的播放

开源 Objective-C IOS 应用开发(十九)视频的播放

开源 Objective-C IOS 应用开发(二十)多线程处理

开源 Objective-C IOS 应用开发(二十一)自定义控件--示波器

开源 Objective-C IOS 应用开发(二十二)自定义控件--车速仪表盘

推荐链接:

开源 Arkts 鸿蒙应用 开发(一)工程文件分析-CSDN博客

开源 Arkts 鸿蒙应用 开发(二)封装库.har制作和应用-CSDN博客

开源 Arkts 鸿蒙应用 开发(三)Arkts的介绍-CSDN博客

开源 Arkts 鸿蒙应用 开发(四)布局和常用控件-CSDN博客

开源 Arkts 鸿蒙应用 开发(五)控件组成和复杂控件-CSDN博客

开源 Arkts 鸿蒙应用 开发(六)数据持久--文件和首选项存储-CSDN博客

开源 Arkts 鸿蒙应用 开发(七)数据持久--sqlite关系数据库-CSDN博客

开源 Arkts 鸿蒙应用 开发(八)多媒体--相册和相机-CSDN博客

开源 Arkts 鸿蒙应用 开发(九)通讯--tcp客户端-CSDN博客

开源 Arkts 鸿蒙应用 开发(十)通讯--Http-CSDN博客

开源 Arkts 鸿蒙应用 开发(十一)证书和包名修改-CSDN博客

开源 Arkts 鸿蒙应用 开发(十二)传感器的使用-CSDN博客

开源 Arkts 鸿蒙应用 开发(十三)音频--MP3播放_arkts avplayer播放音频 mp3-CSDN博客

开源 Arkts 鸿蒙应用 开发(十四)线程--任务池(taskpool)-CSDN博客

开源 Arkts 鸿蒙应用 开发(十五)自定义绘图控件--仪表盘-CSDN博客

开源 Arkts 鸿蒙应用 开发(十六)自定义绘图控件--波形图-CSDN博客

开源 Arkts 鸿蒙应用 开发(十七)通讯--http多文件下载-CSDN博客

开源 Arkts 鸿蒙应用 开发(十八)通讯--Ble低功耗蓝牙服务器-CSDN博客

推荐链接:

开源 java android app 开发(一)开发环境的搭建-CSDN博客

开源 java android app 开发(二)工程文件结构-CSDN博客

开源 java android app 开发(三)GUI界面布局和常用组件-CSDN博客

开源 java android app 开发(四)GUI界面重要组件-CSDN博客

开源 java android app 开发(五)文件和数据库存储-CSDN博客

开源 java android app 开发(六)多媒体使用-CSDN博客

开源 java android app 开发(七)通讯之Tcp和Http-CSDN博客

开源 java android app 开发(八)通讯之Mqtt和Ble-CSDN博客

开源 java android app 开发(九)后台之线程和服务-CSDN博客

开源 java android app 开发(十)广播机制-CSDN博客

开源 java android app 开发(十一)调试、发布-CSDN博客

开源 java android app 开发(十二)封库.aar-CSDN博客

本章内容主要是使用iphone进行视频播放。

目录:

1.手机演示

2.所有源码

3.源码分析

一、手机演示

二、所有源码

AppDelegate.h文件

复制代码
#import <UIKit/UIKit.h>

@class VideoViewController;

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;
@property (strong, nonatomic) VideoViewController *videoViewController;

@end

AppDelegate.m文件

复制代码
#import "AppDelegate.h"
#import "VideoViewController.h"

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // 创建主窗口
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    self.window.backgroundColor = [UIColor whiteColor];
    
    // 创建视频播放视图控制器
    self.videoViewController = [[VideoViewController alloc] init];
    
    // 设置根视图控制器
    self.window.rootViewController = self.videoViewController;
    
    // 显示窗口
    [self.window makeKeyAndVisible];
    
    return YES;
}

@end

VideoPlayer.h文件

复制代码
#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface VideoPlayer : UIView

@property (strong, nonatomic) AVPlayer *player;
@property (strong, nonatomic) AVPlayerLayer *playerLayer;
@property (strong, nonatomic) UIButton *playPauseButton;
@property (strong, nonatomic) UIButton *fullscreenButton;
@property (strong, nonatomic) UIView *controlsView;
@property (strong, nonatomic) UISlider *progressSlider;
@property (strong, nonatomic) UILabel *currentTimeLabel;
@property (strong, nonatomic) UILabel *totalTimeLabel;

- (void)setupPlayerWithURL:(NSURL *)videoURL;
- (void)play;
- (void)pause;
- (void)togglePlayPause;

@end

NS_ASSUME_NONNULL_END

VideoPlayer.m文件

复制代码
#import "VideoPlayer.h"

@interface VideoPlayer()

@property (strong, nonatomic) id timeObserver;
@property (assign, nonatomic) BOOL controlsHidden;

@end

@implementation VideoPlayer

- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        [self setupUI];
        [self setupGestures];
    }
    return self;
}

- (void)setupUI {
    self.backgroundColor = [UIColor blackColor];
    
    // 创建播放器图层
    self.playerLayer = [AVPlayerLayer playerLayerWithPlayer:nil];
    self.playerLayer.videoGravity = AVLayerVideoGravityResizeAspect;
    [self.layer addSublayer:self.playerLayer];
    
    // 创建控制界面
    self.controlsView = [[UIView alloc] init];
    self.controlsView.backgroundColor = [UIColor colorWithWhite:0.0 alpha:0.7];
    [self addSubview:self.controlsView];
    
    // 播放/暂停按钮
    self.playPauseButton = [UIButton buttonWithType:UIButtonTypeSystem];
    [self.playPauseButton setTitle:@"播放" forState:UIControlStateNormal];
    [self.playPauseButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
    [self.playPauseButton addTarget:self action:@selector(togglePlayPause) forControlEvents:UIControlEventTouchUpInside];
    [self.controlsView addSubview:self.playPauseButton];
    
    // 进度条
    self.progressSlider = [[UISlider alloc] init];
    self.progressSlider.minimumValue = 0.0;
    [self.progressSlider addTarget:self action:@selector(sliderValueChanged:) forControlEvents:UIControlEventValueChanged];
    [self.progressSlider addTarget:self action:@selector(sliderTouchUpInside:) forControlEvents:UIControlEventTouchUpInside];
    [self.controlsView addSubview:self.progressSlider];
    
    // 时间标签
    self.currentTimeLabel = [[UILabel alloc] init];
    self.currentTimeLabel.text = @"00:00";
    self.currentTimeLabel.textColor = [UIColor whiteColor];
    self.currentTimeLabel.font = [UIFont systemFontOfSize:12];
    [self.controlsView addSubview:self.currentTimeLabel];
    
    self.totalTimeLabel = [[UILabel alloc] init];
    self.totalTimeLabel.text = @"00:00";
    self.totalTimeLabel.textColor = [UIColor whiteColor];
    self.totalTimeLabel.font = [UIFont systemFontOfSize:12];
    [self.controlsView addSubview:self.totalTimeLabel];
    
    // 全屏按钮
    self.fullscreenButton = [UIButton buttonWithType:UIButtonTypeSystem];
    [self.fullscreenButton setTitle:@"全屏" forState:UIControlStateNormal];
    [self.fullscreenButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
    [self.controlsView addSubview:self.fullscreenButton];
    
    self.controlsHidden = NO;
    [self hideControlsAfterDelay];
}

- (void)setupGestures {
    // 添加点击手势来控制界面显示/隐藏
    UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(toggleControls)];
    [self addGestureRecognizer:tapGesture];
}

- (void)layoutSubviews {
    [super layoutSubviews];
    
    // 设置播放器图层大小
    self.playerLayer.frame = self.bounds;
    
    // 设置控制界面布局
    CGFloat controlsHeight = 60.0;
    self.controlsView.frame = CGRectMake(0, self.bounds.size.height - controlsHeight,
                                        self.bounds.size.width, controlsHeight);
    
    CGFloat padding = 10.0;
    CGFloat buttonWidth = 60.0;
    
    self.playPauseButton.frame = CGRectMake(padding, 10, buttonWidth, 30);
    
    self.currentTimeLabel.frame = CGRectMake(CGRectGetMaxX(self.playPauseButton.frame) + padding, 15, 40, 20);
    
    CGFloat sliderX = CGRectGetMaxX(self.currentTimeLabel.frame) + padding;
    CGFloat fullscreenButtonWidth = 50.0;
    self.fullscreenButton.frame = CGRectMake(self.bounds.size.width - fullscreenButtonWidth - padding, 10,
                                           fullscreenButtonWidth, 30);
    
    self.totalTimeLabel.frame = CGRectMake(CGRectGetMinX(self.fullscreenButton.frame) - 40 - padding, 15, 40, 20);
    
    CGFloat sliderWidth = CGRectGetMinX(self.totalTimeLabel.frame) - sliderX - padding;
    self.progressSlider.frame = CGRectMake(sliderX, 15, sliderWidth, 20);
}

- (void)setupPlayerWithURL:(NSURL *)videoURL {
    // 创建AVPlayerItem
    AVPlayerItem *playerItem = [AVPlayerItem playerItemWithURL:videoURL];
    
    // 创建AVPlayer
    self.player = [AVPlayer playerWithPlayerItem:playerItem];
    self.playerLayer.player = self.player;
    
    // 添加时间观察者
    __weak typeof(self) weakSelf = self;
    self.timeObserver = [self.player addPeriodicTimeObserverForInterval:CMTimeMake(1, 1)
                                                                  queue:dispatch_get_main_queue()
                                                             usingBlock:^(CMTime time) {
        [weakSelf updateProgress];
    }];
    
    // 监听播放结束
    [[NSNotificationCenter defaultCenter] addObserver:self
                                           selector:@selector(playerItemDidReachEnd:)
                                               name:AVPlayerItemDidPlayToEndTimeNotification
                                             object:playerItem];
}

- (void)updateProgress {
    if (self.player.currentItem.status == AVPlayerItemStatusReadyToPlay) {
        Float64 currentTime = CMTimeGetSeconds(self.player.currentTime);
        Float64 duration = CMTimeGetSeconds(self.player.currentItem.duration);
        
        if (isfinite(duration)) {
            self.progressSlider.value = currentTime / duration;
            self.currentTimeLabel.text = [self timeStringFromSeconds:currentTime];
            self.totalTimeLabel.text = [self timeStringFromSeconds:duration];
        }
    }
}

- (NSString *)timeStringFromSeconds:(Float64)seconds {
    NSInteger minutes = (NSInteger)seconds / 60;
    NSInteger secs = (NSInteger)seconds % 60;
    return [NSString stringWithFormat:@"%02ld:%02ld", (long)minutes, (long)secs];
}

- (void)play {
    [self.player play];
    [self.playPauseButton setTitle:@"暂停" forState:UIControlStateNormal];
}

- (void)pause {
    [self.player pause];
    [self.playPauseButton setTitle:@"播放" forState:UIControlStateNormal];
}

- (void)togglePlayPause {
    if (self.player.rate == 0.0) {
        [self play];
    } else {
        [self pause];
    }
}

- (void)sliderValueChanged:(UISlider *)slider {
    [self.player removeTimeObserver:self.timeObserver];
}

- (void)sliderTouchUpInside:(UISlider *)slider {
    if (self.player.currentItem.status == AVPlayerItemStatusReadyToPlay) {
        Float64 duration = CMTimeGetSeconds(self.player.currentItem.duration);
        Float64 seekTime = duration * slider.value;
        
        [self.player seekToTime:CMTimeMakeWithSeconds(seekTime, NSEC_PER_SEC)];
        
        // 重新添加时间观察者
        __weak typeof(self) weakSelf = self;
        self.timeObserver = [self.player addPeriodicTimeObserverForInterval:CMTimeMake(1, 1)
                                                                      queue:dispatch_get_main_queue()
                                                                 usingBlock:^(CMTime time) {
            [weakSelf updateProgress];
        }];
    }
}

- (void)playerItemDidReachEnd:(NSNotification *)notification {
    [self.player seekToTime:kCMTimeZero];
    [self pause];
}

- (void)toggleControls {
    self.controlsHidden = !self.controlsHidden;
    
    [UIView animateWithDuration:0.3 animations:^{
        self.controlsView.alpha = self.controlsHidden ? 0.0 : 1.0;
    }];
    
    if (!self.controlsHidden) {
        [self hideControlsAfterDelay];
    }
}

- (void)hideControlsAfterDelay {
    [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(hideControls) object:nil];
    [self performSelector:@selector(hideControls) withObject:nil afterDelay:3.0];
}

- (void)hideControls {
    [UIView animateWithDuration:0.3 animations:^{
        self.controlsView.alpha = 0.0;
    }];
    self.controlsHidden = YES;
}

- (void)dealloc {
    if (self.timeObserver) {
        [self.player removeTimeObserver:self.timeObserver];
    }
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

@end

VideoViewController.h文件

复制代码
#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface VideoViewController : UIViewController

@end

NS_ASSUME_NONNULL_END

VideoViewController.m文件

复制代码
#import "VideoViewController.h"
#import "VideoPlayer.h"

@interface VideoViewController ()

@property (strong, nonatomic) VideoPlayer *videoPlayer;

@end

@implementation VideoViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self setupUI];
    [self setupVideoPlayer];
}

- (void)setupUI {
    self.view.backgroundColor = [UIColor whiteColor];
    
    // 创建标题
    UILabel *titleLabel = [[UILabel alloc] init];
    titleLabel.text = @"视频播放器";
    titleLabel.font = [UIFont boldSystemFontOfSize:24];
    titleLabel.textAlignment = NSTextAlignmentCenter;
    titleLabel.frame = CGRectMake(0, 60, self.view.bounds.size.width, 40);
    [self.view addSubview:titleLabel];
}

- (void)setupVideoPlayer {
    // 创建视频播放器
    CGFloat playerWidth = self.view.bounds.size.width - 40;
    CGFloat playerHeight = playerWidth * 9.0 / 16.0; // 16:9 比例
    
    self.videoPlayer = [[VideoPlayer alloc] initWithFrame:CGRectMake(20, 120, playerWidth, playerHeight)];
    [self.view addSubview:self.videoPlayer];
    
    // 使用本地视频文件或网络视频URL
    NSURL *videoURL = nil;
    
    // 方式1: 使用本地视频文件 (需要将视频文件添加到项目中)
    NSString *localVideoPath = [[NSBundle mainBundle] pathForResource:@"sample" ofType:@"mp4"];
    if (localVideoPath) {
        videoURL = [NSURL fileURLWithPath:localVideoPath];
    } else {
        // 方式2: 使用网络视频URL
        videoURL = [NSURL URLWithString:@"https://example.com/sample.mp4"];
        
        // 方式3: 使用测试视频URL (这是一个公开的测试视频)
        //videoURL = [NSURL URLWithString:@"https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"];
        
    }
    
    if (videoURL) {
        [self.videoPlayer setupPlayerWithURL:videoURL];
    } else {
        NSLog(@"无法找到视频文件");
    }
    
    // 添加说明标签
    UILabel *instructionLabel = [[UILabel alloc] init];
    instructionLabel.text = @"点击视频区域显示/隐藏控制界面";
    instructionLabel.font = [UIFont systemFontOfSize:14];
    instructionLabel.textAlignment = NSTextAlignmentCenter;
    instructionLabel.textColor = [UIColor grayColor];
    instructionLabel.frame = CGRectMake(20, CGRectGetMaxY(self.videoPlayer.frame) + 20,
                                      self.view.bounds.size.width - 40, 30);
    [self.view addSubview:instructionLabel];
}

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    
    // 开始播放视频
    [self.videoPlayer play];
}

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    
    // 暂停播放视频
    [self.videoPlayer pause];
}

- (BOOL)shouldAutorotate {
    return YES;
}

- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
    return UIInterfaceOrientationMaskPortrait;
}

@end

info.plist

复制代码
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
    
    <dict>
        <key>NSAllowsArbitraryLoads</key>
        <true/>
    </dict>
</plist>

三、源码分析

VideoPlayer.h 头文件分析

属性声明

objectivec

@property (strong, nonatomic) AVPlayer *player; // AVFoundation播放器核心

@property (strong, nonatomic) AVPlayerLayer *playerLayer; // 视频显示图层

@property (strong, nonatomic) UIButton *playPauseButton; // 播放/暂停按钮

@property (strong, nonatomic) UIButton *fullscreenButton; // 全屏按钮(目前无功能)

@property (strong, nonatomic) UIView *controlsView; // 控制界面容器

@property (strong, nonatomic) UISlider *progressSlider; // 进度条

@property (strong, nonatomic) UILabel *currentTimeLabel; // 当前时间显示

@property (strong, nonatomic) UILabel *totalTimeLabel; // 总时间显示

公共方法

objectivec

  • (void)setupPlayerWithURL:(NSURL *)videoURL; // 初始化播放器并设置视频URL

  • (void)play; // 开始播放

  • (void)pause; // 暂停播放

  • (void)togglePlayPause; // 切换播放/暂停状态

VideoPlayer.m 实现文件分析

  1. 初始化函数

objectivec

  • (instancetype)initWithFrame:(CGRect)frame {

self = super initWithFrame:frame;

if (self) {

self setupUI; // 初始化用户界面

self setupGestures; // 设置手势识别

}

return self;

}

  1. 界面设置函数

objectivec

  • (void)setupUI {

// 设置黑色背景

self.backgroundColor = UIColor blackColor;

// 创建播放器图层

self.playerLayer = AVPlayerLayer playerLayerWithPlayer:nil;

self.playerLayer.videoGravity = AVLayerVideoGravityResizeAspect; // 保持视频比例

self.layer addSublayer:self.playerLayer;

// 创建半透明控制界面

self.controlsView = \[UIView alloc init];

self.controlsView.backgroundColor = UIColor colorWithWhite:0.0 alpha:0.7;

// 创建播放/暂停按钮

self.playPauseButton = UIButton buttonWithType:UIButtonTypeSystem;

self.playPauseButton setTitle:@"播放" forState:UIControlStateNormal;

self.playPauseButton addTarget:self action:@selector(togglePlayPause) forControlEvents:UIControlEventTouchUpInside;

// 创建进度条并绑定事件

self.progressSlider = \[UISlider alloc init];

self.progressSlider addTarget:self action:@selector(sliderValueChanged:) forControlEvents:UIControlEventValueChanged;

self.progressSlider addTarget:self action:@selector(sliderTouchUpInside:) forControlEvents:UIControlEventTouchUpInside;

// 创建时间显示标签

self.currentTimeLabel = \[UILabel alloc init];

self.currentTimeLabel.text = @"00:00";

self.totalTimeLabel = \[UILabel alloc init];

self.totalTimeLabel.text = @"00:00";

// 创建全屏按钮(目前只有UI,无功能)

self.fullscreenButton = UIButton buttonWithType:UIButtonTypeSystem;

self.fullscreenButton setTitle:@"全屏" forState:UIControlStateNormal;

// 初始化控制界面状态并设置自动隐藏

self.controlsHidden = NO;

self hideControlsAfterDelay;

}

  1. 手势设置函数

objectivec

  • (void)setupGestures {

// 添加点击手势用于显示/隐藏控制界面

UITapGestureRecognizer *tapGesture = \[UITapGestureRecognizer alloc

initWithTarget:self

action:@selector(toggleControls)];

self addGestureRecognizer:tapGesture;

}

  1. 布局函数

objectivec

  • (void)layoutSubviews {

super layoutSubviews;

// 设置播放器图层充满整个视图

self.playerLayer.frame = self.bounds;

// 设置控制界面在底部

CGFloat controlsHeight = 60.0;

self.controlsView.frame = CGRectMake(0, self.bounds.size.height - controlsHeight,

self.bounds.size.width, controlsHeight);

// 详细布局各个控件的位置和大小

// 从左到右:播放按钮 -> 当前时间 -> 进度条 -> 总时间 -> 全屏按钮

}

  1. 播放器初始化函数

objectivec

  • (void)setupPlayerWithURL:(NSURL *)videoURL {

// 创建播放项目

AVPlayerItem *playerItem = AVPlayerItem playerItemWithURL:videoURL;

// 创建播放器实例

self.player = AVPlayer playerWithPlayerItem:playerItem;

self.playerLayer.player = self.player;

// 添加时间观察者,每秒更新一次进度

__weak typeof(self) weakSelf = self;

self.timeObserver = [self.player addPeriodicTimeObserverForInterval:CMTimeMake(1, 1)

queue:dispatch_get_main_queue()

usingBlock:^(CMTime time) {

weakSelf updateProgress; // 更新进度显示

}];

// 监听播放结束通知

\[NSNotificationCenter defaultCenter addObserver:self

selector:@selector(playerItemDidReachEnd:)

name:AVPlayerItemDidPlayToEndTimeNotification

object:playerItem];

}

  1. 进度更新函数

objectivec

  • (void)updateProgress {

if (self.player.currentItem.status == AVPlayerItemStatusReadyToPlay) {

// 获取当前播放时间和总时长

Float64 currentTime = CMTimeGetSeconds(self.player.currentTime);

Float64 duration = CMTimeGetSeconds(self.player.currentItem.duration);

if (isfinite(duration)) {

// 更新进度条值

self.progressSlider.value = currentTime / duration;

// 更新时间显示

self.currentTimeLabel.text = self timeStringFromSeconds:currentTime;

self.totalTimeLabel.text = self timeStringFromSeconds:duration;

}

}

}

  1. 时间格式化函数

objectivec

  • (NSString *)timeStringFromSeconds:(Float64)seconds {

// 将秒数转换为 "分:秒" 格式

NSInteger minutes = (NSInteger)seconds / 60;

NSInteger secs = (NSInteger)seconds % 60;

return NSString stringWithFormat:@"%02ld:%02ld", (long)minutes, (long)secs;

}

  1. 播放控制函数

objectivec

  • (void)play {

self.player play;

self.playPauseButton setTitle:@"暂停" forState:UIControlStateNormal; // 更新按钮文字

}

  • (void)pause {

self.player pause;

self.playPauseButton setTitle:@"播放" forState:UIControlStateNormal; // 更新按钮文字

}

  • (void)togglePlayPause {

// 根据当前播放状态切换

if (self.player.rate == 0.0) {

self play;

} else {

self pause;

}

}

  1. 进度条控制函数

objectivec

  • (void)sliderValueChanged:(UISlider *)slider {

// 拖动进度条时移除时间观察者,避免冲突

self.player removeTimeObserver:self.timeObserver;

}

  • (void)sliderTouchUpInside:(UISlider *)slider {

if (self.player.currentItem.status == AVPlayerItemStatusReadyToPlay) {

// 计算要跳转的时间点

Float64 duration = CMTimeGetSeconds(self.player.currentItem.duration);

Float64 seekTime = duration * slider.value;

// 执行跳转

self.player seekToTime:CMTimeMakeWithSeconds(seekTime, NSEC_PER_SEC);

// 重新添加时间观察者

__weak typeof(self) weakSelf = self;

self.timeObserver = [self.player addPeriodicTimeObserverForInterval:CMTimeMake(1, 1)

queue:dispatch_get_main_queue()

usingBlock:^(CMTime time) {

weakSelf updateProgress;

}];

}

}

  1. 播放结束处理函数

objectivec

  • (void)playerItemDidReachEnd:(NSNotification *)notification {

// 播放结束时回到开头并暂停

self.player seekToTime:kCMTimeZero;

self pause;

}

  1. 控制界面显示/隐藏函数

objectivec

  • (void)toggleControls {

// 切换控制界面显示状态

self.controlsHidden = !self.controlsHidden;

[UIView animateWithDuration:0.3 animations:^{

self.controlsView.alpha = self.controlsHidden ? 0.0 : 1.0;

}];

// 如果显示控制界面,设置3秒后自动隐藏

if (!self.controlsHidden) {

self hideControlsAfterDelay;

}

}

  • (void)hideControlsAfterDelay {

// 取消之前的隐藏请求,避免重复

NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(hideControls) object:nil;

// 3秒后执行隐藏

self performSelector:@selector(hideControls) withObject:nil afterDelay:3.0;

}

  • (void)hideControls {

// 隐藏控制界面

[UIView animateWithDuration:0.3 animations:^{

self.controlsView.alpha = 0.0;

}];

self.controlsHidden = YES;

}

  1. 清理函数

objectivec

  • (void)dealloc {

// 移除时间观察者

if (self.timeObserver) {

self.player removeTimeObserver:self.timeObserver;

}

// 移除通知观察者

\[NSNotificationCenter defaultCenter removeObserver:self];

}

VideoViewController 分析

主要函数

objectivec

  • (void)viewDidLoad {

super viewDidLoad;

self setupUI; // 设置界面标题

self setupVideoPlayer; // 初始化视频播放器

}

  • (void)setupVideoPlayer {

// 创建16:9比例的视频播放器

CGFloat playerWidth = self.view.bounds.size.width - 40;

CGFloat playerHeight = playerWidth * 9.0 / 16.0;

self.videoPlayer = \[VideoPlayer alloc initWithFrame:CGRectMake(20, 120, playerWidth, playerHeight)];

self.view addSubview:self.videoPlayer;

// 尝试加载视频文件(本地→网络→测试URL)

// 设置播放器并开始播放

}

相关推荐
阿里云云原生3 小时前
HiRoute 开源:让 Agent 更能干,让模型订阅更耐用
开源
分布式存储与RustFS3 小时前
rclone 与 mc mirror:S3 对象存储同步,限速、过滤、断点续传实测对比
运维·云原生·开源·对象存储·分布式存储·s3·性能基准
小弥儿5 小时前
GitHub今日热榜 | 2026-10-12:pytorch 上升四位
人工智能·pytorch·学习·开源·github
Coder_张三5 小时前
iOS 26 适配
前端·ios·swift
TTATTC5 小时前
Android 浏览器死活不弹证书框的那天,我用 .NET MAUI 给 WebView 写了一个 mTLS 拦截器
docker·开源
魔众5 小时前
1GB 显存跑声音克隆,LuxTTS 上了 AIGCPanel
开源
TTATTC6 小时前
百万行 Trace 交给 AI,方向和验证留给人,一套还原自定义 VMP 保护的分工方法
docker·开源
鶴哥只手遮天6 小时前
ecRad 1.5.0 算法原理详解
算法·开源
用户38034165882976 小时前
崩溃率从 0.3% 到 0.04%:我们做对的不是修 bug,是让每个崩溃只发生一次
ios·swift
TTATTC6 小时前
从 proxy_pass 末尾斜杠到 WebAssembly 预压缩:深挖 Nginx 生产环境四大高频踩坑点
docker·开源