iOS开发 - 用AVPlayer封装一个播放器

简介: iOS开发 - 用AVPlayer封装一个播放器

因为GIF上传的时候一直失败,所以大家到Github查看效果图

https://github.com/codeliu6572/LHHVideoPlayer

由于亮度和音量只能在真机上显现效果,所以GIF未给出操作,其

中快进快退区域为当前播放器宽度的一半,高度为播放器高度,

音量和亮度上下滑动区域总和为剩下的一半区域,且二者大小一样。


播放器基本功能:


1、视频播放和缓存(不支持流播放);

2、双击屏幕全屏和竖屏;

3、快进快退;

4、滑动调节屏幕亮度和系统声音;

5、全屏按钮和返回按钮;

6、进度条拖动和点击定位播放;

7、完美适配横竖屏;

8、打断机制和监听机制;

9、dealloc;

10、总结;


1.视频播放和缓存(不支持流播放);


创建播放器:

  // 3.playerItem关联创建player
    self.player = [AVPlayer playerWithPlayerItem:self.playItem];
    self.playerView = [[LHHPlayerView alloc]initWithFrame:CGRectMake(0, 0, VIEWWIDTH, 240)];
    // 4.player关联创建playerView
    // 4.player关联创建playerView
    [self.playerView setPlayer:self.player];
    [self.playerView.layer setBackgroundColor:[UIColor blackColor].CGColor];
    //添加播放视图到self.view
    [self.view addSubview:self.playerView];

为了能够在播放器的表层加上控件(AVPlayer原生不能往上加控件无效且没有交互属性),需要对它进行一个小操作:

#import <UIKit/UIKit.h>
@class AVPlayer;
@interface LHHPlayerView : UIView
@property (nonatomic, strong) AVPlayer *player;
@end
#import "LHHPlayerView.h"
#import <AVFoundation/AVFoundation.h>
@interface LHHPlayerView ()
@end
@implementation LHHPlayerView
// 为了使PlayerView的layer为AVPlayerLayer类型
+ (Class)layerClass {
    return [AVPlayerLayer class];
}
- (AVPlayer *)player {
    return [(AVPlayerLayer *)[self layer] player];
}
- (void)setPlayer:(AVPlayer *)player {
    [(AVPlayerLayer *)[self layer] setPlayer:player];
}
- (void)dealloc
{
    self.player = nil;
}
@end

然后用这个处理过的View作为AVPlayerLayer,此时可以往上增加控件。关于缓存,系统的播放器对于http连接的视频是会自动缓存的,不需要我们做什么操作,不妨下载Demo断网试一试。


2.双击屏幕全屏和竖屏;


这里的横竖屏机制请看这篇博客: http://blog.csdn.net/codingfire/article/details/50387774 按照上面博客的配置好之后,需要设置如下代码:

#pragma mark - 屏幕方向
// 允许自动旋转,在支持的屏幕中设置了允许旋转的屏幕方向。
- (BOOL)shouldAutorotate
{
    return YES;
}
// 支持的屏幕方向,这个方法返回 UIInterfaceOrientationMaskAllButUpsideDown 类型的值。
- (NSUInteger)supportedInterfaceOrientations
{
    return UIInterfaceOrientationMaskAllButUpsideDown;
}
// 视图展示的时候优先展示为 竖屏,可更改
-(UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
{
    return UIInterfaceOrientationPortrait;
}

博主这里屏幕给了小屏,没有直接全屏,按自己需求更改播放器的播放界面frame,同时在横竖屏切换时要适时的刷新界面上UI:

根据重力感应手机方向来切换屏幕需要用到这个方法:
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation duration:(NSTimeInterval)duration {
    switch (interfaceOrientation) {
        case UIInterfaceOrientationPortrait:
            //home健在下
            self.playerView.frame = CGRectMake(0, 0, VIEWWIDTH, 220);
            _bigView = NO;
            break;
        case UIInterfaceOrientationPortraitUpsideDown:
            //home健在上
            break;
        case UIInterfaceOrientationLandscapeLeft:
            //home健在左
            self.playerView.frame = CGRectMake(0, 0, VIEWWIDTH, VIEWHEIGTH);
            _bigView = YES;
            break;
        case UIInterfaceOrientationLandscapeRight:
            //home健在右
            self.playerView.frame = CGRectMake(0, 0, VIEWWIDTH, VIEWHEIGTH);
            _bigView = YES;
            break;
        default:
            break;
    }
    [self layoutUI];
}

下面是通过双击屏幕和全屏按钮来控制横竖屏(也可根据需要适时退出界面):

#pragma mark - backAction 返回按钮功能
- (void)backBtnAction
{
    if (_bigView == YES) {
        [[UIDevice currentDevice] setValue:[NSNumber numberWithInteger:UIDeviceOrientationPortrait]forKey:@"orientation"];
        self.playerView.frame = CGRectMake(0, 0, VIEWWIDTH, 220);
        _bigView = NO;
        [self layoutUI];
    }
    else
    {
        AppDelegate *appdelegate=(AppDelegate *)[UIApplication sharedApplication].delegate;
        appdelegate.allowRotation=NO;
        [self.player pause];
        [self.playerView removeFromSuperview];
        [self.navigationController popViewControllerAnimated:YES];
    }
}
#pragma mark - bigViewBtnAction
- (void)bigViewBtnAction  全屏按钮功能
{
    if (_bigView == NO) {
        self.playerView.frame = CGRectMake(0, 0, VIEWWIDTH, VIEWHEIGTH);
        [[UIDevice currentDevice] setValue:[NSNumber numberWithInteger:UIDeviceOrientationLandscapeLeft]forKey:@"orientation"];
        _bigView = YES;
    }
    else
    {
        self.playerView.frame = CGRectMake(0, 0, VIEWWIDTH, 220);
        [[UIDevice currentDevice] setValue:[NSNumber numberWithInteger:UIDeviceOrientationPortrait]forKey:@"orientation"];
        _bigView = NO;
    }
    [self layoutUI];
}

改变UI其实不需要改变什么,就是刷新下控件的位置,淫秽横竖屏宽高交换原则,相当于重新给一个frame,原值保持不变,其中的变量随着播放器frame改变而改变:

#pragma mark layoutUI
- (void)layoutUI
{
    _headerView.frame = CGRectMake(0 ,0, VIEWWIDTH, 40);
    _bottomView.frame = CGRectMake(0 ,self.playerView.bounds.size.height - 50, VIEWWIDTH, 50);
    _bigViewBtn.frame = CGRectMake(_headerView.bounds.size.width - 44 ,0, 44, 40);
    _progressView.frame = CGRectMake(45, 21, VIEWWIDTH - 60 - 80, 10);
    _currentTimeLabel.frame = CGRectMake(VIEWWIDTH - 90, 5, 45, 40);
    _remainTimeLabel.frame = CGRectMake(VIEWWIDTH - 45, 5, 45, 40);
    _voiceLight.frame = self.playerView.bounds;
}


3.快进快退;

#pragma mark 快退
- (void)fastBackward {
    [self cancelPerformSelector:@selector(hideHeaderViewAndBottomView)];
    [self progressAdd:-OFFSET];
    [self delayHideHeaderViewAndBottomView];
}
#pragma mark 快进
- (void)fastForward {
    [self cancelPerformSelector:@selector(hideHeaderViewAndBottomView)];
    [self progressAdd:OFFSET];
    [self delayHideHeaderViewAndBottomView];
}
- (void)progressAdd:(CGFloat)step {
    // 如果正在播放先暂停播放(但是不改变_playing的值为NO,因为快进或快退完成后要根据_playing来判断是否要继续播放),再进行播放定位
    if (_isPlaying) {
        [self.player pause];
    }
    Float64 currentSecond = CMTimeGetSeconds(self.player.currentTime); // 当前秒
    Float64 totalSeconds = CMTimeGetSeconds(self.duration); // 总时间
    CMTime dstTime; // 目标时间
    if (currentSecond + step >= totalSeconds) {
        dstTime = CMTimeSubtract(self.duration, CMTimeMakeWithSeconds(1, self.duration.timescale));
        self.progressView.value = dstTime.value / self.duration.value;
    } else if (currentSecond + step < 0.0) {
        dstTime = kCMTimeZero;
        self.progressView.value = 0.0;
    } else {
        dstTime = CMTimeMakeWithSeconds(currentSecond + step, self.player.currentTime.timescale);
        self.progressView.value += step / CMTimeGetSeconds(self.duration);
    }
    [self seekToCMTime:dstTime progress:self.progressView.value];
    if (_isPlaying) {
        [self.player play];
    }
}

利用Swipe滑动手势来设定播放节点,找到播放位置,手势和控制屏幕亮度封装在了一起,通过一个代理来调用。


4.滑动调节屏幕亮度和系统声音;


关于这个,请查看下面这篇博客: http://blog.csdn.net/codingfire/article/details/53810649 里面详细说明了怎么通过滑动来控制系统声音和屏幕亮度,并有Demo供参考。


5.全屏按钮和返回按钮;


这里的功能上面已经给出,需要注意的就是因为没有使用约束,所以每次横竖屏切换都要用方法自动或者手动来刷新界面控件的位置甚至图片,在代码中可以查看。


6.进度条拖动和点击定位播放;


进度条用slider来完成,自定义UI,通过拖动的位置value属性和视频总时间判断当前播放时间和进度条位置,slider增加点击手势,通过点击位置瞬间定位到所点击的位置播放:

 //progressView初始化
    _progressView = [[UISlider alloc]initWithFrame:CGRectMake(45, 21, VIEWWIDTH - 60 - 80, 10)];
    [_progressView addTarget:self action:@selector(slidingProgress:) forControlEvents:UIControlEventValueChanged];
    [_progressView addTarget:self action:@selector(slidingEnded:) forControlEvents:UIControlEventTouchUpInside];
    [_progressView addTarget:self action:@selector(slidingEnded:) forControlEvents:UIControlEventTouchUpOutside];
    [_bottomView addSubview:_progressView];
//进度条UI定制
  [self.progressView setMinimumTrackImage:[[UIImage imageNamed:@"video_num_front.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 5) resizingMode:UIImageResizingModeStretch] forState:UIControlStateNormal];
    [self.progressView setMaximumTrackImage:[[UIImage imageNamed:@"video_num_bg.png"]  resizableImageWithCapInsets:UIEdgeInsetsMake(0, 5, 0, 5) resizingMode:UIImageResizingModeStretch] forState:UIControlStateNormal];
    [self.progressView setThumbImage:[UIImage imageNamed:@"progressThumb.png"] forState:UIControlStateNormal];
#pragma mark - 拖动进度条改变播放点(playhead)
// valueChanged
- (void)slidingProgress:(UISlider *)slider {
    // 取消调用hideHeaderViewAndBottomView方法,不隐藏
    [self cancelPerformSelector:@selector(hideHeaderViewAndBottomView)];
    Float64 totalSeconds = CMTimeGetSeconds(self.duration);
    CMTime time = CMTimeMakeWithSeconds(totalSeconds * slider.value, self.duration.timescale);
    [self seekToCMTime:time progress:self.progressView.value];
}
// touchUpInside/touchUpOutside
- (void)slidingEnded:(UISlider *)sender {
    // 拖动手势取消后延迟调用hideHeaderViewAndBottomView
    [self delayHideHeaderViewAndBottomView];
}
//进度条点击定位
- (void)sliderTapGesture:(UITapGestureRecognizer *)sender {
    [self cancelPerformSelector:@selector(hideHeaderViewAndBottomView)];
    CGFloat tapX = [sender locationInView:sender.view].x;
    CGFloat sliderWidth = sender.view.bounds.size.width;
    Float64 totalSeconds = CMTimeGetSeconds(self.duration); // 总时间
    CMTime dstTime = CMTimeMakeWithSeconds(totalSeconds * (tapX / sliderWidth), self.duration.timescale);
    [self seekToCMTime:dstTime progress:self.progressView.value];
    [self delayHideHeaderViewAndBottomView];
}


7.完美适配横竖屏;


很多时候大家注意的是手机方向改变时横竖屏切换和整个项目内横竖屏的关系,上面给出了方法来解决,在Appdelegate中给属性来决定是否支持横屏,这里还有一个手动的通过代码来设置的方法:

[[UIDevice currentDevice] setValue:[NSNumber numberWithInteger:UIDeviceOrientationLandscapeLeft]forKey:@"orientation"];

可以根据自己的需要设置屏幕方向,前提是Xcode中设置了支持该方向。


8.打断机制和监听机制;


   // 监控 app 活动状态,打电话/锁屏 时暂停播放
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appWillResignActive:) name:UIApplicationWillResignActiveNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appDidBecomActive:) name:UIApplicationDidBecomeActiveNotification object:nil];
- (void)appWillResignActive:(NSNotification *)aNotification {
    [self.player pause];
    self.isPlaying = NO;
}
- (void)appDidBecomActive:(NSNotification *)aNotification {
    //
    [self.player play];
    self.isPlaying = YES;
}

KVO监听

 // KVO观察self.isPlaying属性的变化以改变playButton的状态
    [self addObserver:self forKeyPath:@"isPlaying" options:NSKeyValueObservingOptionNew context:playerPlayingContext];
// 观察self.playItem.status属性变化,变为AVPlayerItemStatusReadyToPlay时就可以播放了
    [self addObserver:self forKeyPath:@"playItem.status" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:playerItemStatusContext];
#pragma mark - KVO
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if (context == playerItemStatusContext) {
        if (self.playItem.status == AVPlayerItemStatusReadyToPlay) {
            // 视频准备就绪
            dispatch_async(dispatch_get_main_queue(), ^{
                [self readyToPlay];
            });
        } else {
            // 如果一个不能播放的视频资源加载进来会进到这里
            NSLog(@"视频无法播放");
            // 延迟dismiss播放器视图控制器
            [self performSelector:@selector(delayDismissPlayerViewController) withObject:nil afterDelay:3.0f];
        }
    } else if (context == playerPlayingContext){
        dispatch_async(dispatch_get_main_queue(), ^{
            if ([[change objectForKey:@"new"] intValue] == 1) {
                // 如果playing变为YES就显示暂停按钮
                [self.playButton setImage:[[UIImage imageNamed:@"pause_nor@2x.png"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal] forState:UIControlStateNormal];
            } else {
                // 如果playing变为NO就显示播放按钮
                [self.playButton setImage:[[UIImage imageNamed:@"play_nor@2x.png"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal] forState:UIControlStateNormal];
            }
        });
    } else {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}


9.dealloc;


最后的最后,别忘了dealloc,虽然目前有自动回收机制,不过类似视频这种的,貌似是没用的,需要手动来释放内存,删除监听和通知。 ``` #pragma mark - dealloc

(void)dealloc

{

[self.player pause];

[self removeObserver:self forKeyPath:@“playItem.status” context:playerItemStatusContext];

[self removeObserver:self forKeyPath:@“isPlaying” context:playerPlayingContext];

[self.player removeTimeObserver:self.timeObserver];

self.timeObserver = nil;

[[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:nil];

[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillResignActiveNotification object:nil];

[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil];

self.player = nil;

self.playItem = nil;

self.mediaUrl = nil;

}

<p id="j">10.总结;</p>
以上便是博主封装AVPlayer的过程,不过其中也借鉴了很多东西,有一个叫JRVideoPlayer的开源Demo,博主的内容很多都是从这里来的,有些方法因为写的很完善索性直接拿过来用了,基本上是在原基础上做了优化和新增功能,前人植树,后人乘凉,踩着大神的脚步前进,这里也是因为博主项目中需要用到缓存才去做了封装,功能都是另外加的,UI因为我们的设计就是这样,所以列为不要嫌丑,可自行更换UI,刚好这个播放器可塑性很强,还有下载地址:[点击下载](https://github.com/codeliu6572/LHHVideoPlayer)
博主没有做本地视频的播放,只有网络视频的方法,后续会补上,这部分也不复杂,也可自行加上去。
有一点要着重说明,如果你需要播放器进去就是全屏,就在播放器的VC中改变frame为全屏,同时根据第7条的方法设置默认为横屏,如果你愿意竖屏也行,另外还需要把layoutUI中竖屏的frame改成全屏或者直接删除。如果你的详情页需要现在这样的小窗,下面展示信息和评论等,你需要在外部创建一个UIView,在VC中视频下面的区域放置这个UIView不需要改变frame,视频全屏时会遮住这个View,不用怕下面的界面混乱,以上是两种情况的思路,各位请自便。
目录
相关文章
|
4天前
|
JavaScript 搜索推荐 Android开发
【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
23 8
【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
|
1月前
|
iOS开发 开发者
uniapp开发ios打包Error code = -5000 Error message: Error: certificate file(p12) import failed!报错问题如何解决
uniapp开发ios打包Error code = -5000 Error message: Error: certificate file(p12) import failed!报错问题如何解决
143 67
uniapp开发ios打包Error code = -5000 Error message: Error: certificate file(p12) import failed!报错问题如何解决
|
2月前
|
存储 监控 API
app开发之安卓Android+苹果ios打包所有权限对应解释列表【长期更新】-以及默认打包自动添加权限列表和简化后的基本打包权限列表以uniapp为例-优雅草央千澈
app开发之安卓Android+苹果ios打包所有权限对应解释列表【长期更新】-以及默认打包自动添加权限列表和简化后的基本打包权限列表以uniapp为例-优雅草央千澈
90 11
|
2月前
|
iOS开发 开发者 MacOS
深入探索iOS开发中的SwiftUI框架
【10月更文挑战第21天】 本文将带领读者深入了解Apple最新推出的SwiftUI框架,这一革命性的用户界面构建工具为iOS开发者提供了一种声明式、高效且直观的方式来创建复杂的用户界面。通过分析SwiftUI的核心概念、主要特性以及在实际项目中的应用示例,我们将展示如何利用SwiftUI简化UI代码,提高开发效率,并保持应用程序的高性能和响应性。无论你是iOS开发的新手还是有经验的开发者,本文都将为你提供宝贵的见解和实用的指导。
145 66
|
2月前
|
开发框架 Android开发 iOS开发
安卓与iOS开发中的跨平台策略:一次编码,多平台部署
在移动应用开发的广阔天地中,安卓和iOS两大阵营各占一方。随着技术的发展,跨平台开发框架应运而生,它们承诺着“一次编码,到处运行”的便捷。本文将深入探讨跨平台开发的现状、挑战以及未来趋势,同时通过代码示例揭示跨平台工具的实际运用。
173 3
|
2月前
|
Java 调度 Android开发
安卓与iOS开发中的线程管理差异解析
在移动应用开发的广阔天地中,安卓和iOS两大平台各自拥有独特的魅力。如同东西方文化的差异,它们在处理多线程任务时也展现出不同的哲学。本文将带你穿梭于这两个平台之间,比较它们在线程管理上的核心理念、实现方式及性能考量,助你成为跨平台的编程高手。
|
3月前
|
存储 前端开发 Swift
探索iOS开发:从新手到专家的旅程
本文将带您领略iOS开发的奇妙之旅,从基础概念的理解到高级技巧的掌握,逐步深入iOS的世界。文章不仅分享技术知识,还鼓励读者在编程之路上保持好奇心和创新精神,实现个人成长与技术突破。
|
3月前
|
安全 IDE Swift
探索iOS开发之旅:从初学者到专家
在这篇文章中,我们将一起踏上iOS开发的旅程,从基础概念的理解到深入掌握核心技术。无论你是编程新手还是希望提升技能的开发者,这里都有你需要的指南和启示。我们将通过实际案例和代码示例,展示如何构建一个功能齐全的iOS应用。准备好了吗?让我们一起开始吧!
|
3月前
|
开发框架 前端开发 Android开发
安卓与iOS开发中的跨平台策略
在移动应用开发的战场上,安卓和iOS两大阵营各据一方。随着技术的演进,跨平台开发框架成为开发者的新宠,旨在实现一次编码、多平台部署的梦想。本文将探讨跨平台开发的优势与挑战,并分享实用的开发技巧,帮助开发者在安卓和iOS的世界中游刃有余。
|
3月前
|
安全 Swift iOS开发
Swift 与 UIKit 在 iOS 应用界面开发中的关键技术和实践方法
本文深入探讨了 Swift 与 UIKit 在 iOS 应用界面开发中的关键技术和实践方法。Swift 以其简洁、高效和类型安全的特点,结合 UIKit 丰富的组件和功能,为开发者提供了强大的工具。文章从 Swift 的语法优势、类型安全、编程模型以及与 UIKit 的集成,到 UIKit 的主要组件和功能,再到构建界面的实践技巧和实际案例分析,全面介绍了如何利用这些技术创建高质量的用户界面。
72 2

热门文章

最新文章

  • 1
    【03】仿站技术之python技术,看完学会再也不用去购买收费工具了-修改整体页面做好安卓下载发给客户-并且开始提交网站公安备案-作为APP下载落地页文娱产品一定要备案-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
  • 2
    【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
  • 3
    Cellebrite UFED 4PC 7.71 (Windows) - Android 和 iOS 移动设备取证软件
  • 4
    【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
  • 5
    【01】噩梦终结flutter配安卓android鸿蒙harmonyOS 以及next调试环境配鸿蒙和ios真机调试环境-flutter项目安卓环境配置-gradle-agp-ndkVersion模拟器运行真机测试环境-本地环境搭建-如何快速搭建android本地运行环境-优雅草卓伊凡-很多人在这步就被难倒了
  • 6
    iOS8 中无需开源库的内置功能一览
  • 7
    iOS7应用开发7:自定义视图、手势操作
  • 8
    IOS小工具以及精彩的博客
  • 9
    Facebook SDK(iOS)初学讲解
  • 10
    iOS - Swift NSPoint 位置
  • 1
    【01】噩梦终结flutter配安卓android鸿蒙harmonyOS 以及next调试环境配鸿蒙和ios真机调试环境-flutter项目安卓环境配置-gradle-agp-ndkVersion模拟器运行真机测试环境-本地环境搭建-如何快速搭建android本地运行环境-优雅草卓伊凡-很多人在这步就被难倒了
    14
  • 2
    Cellebrite UFED 4PC 7.71 (Windows) - Android 和 iOS 移动设备取证软件
    28
  • 3
    【03】仿站技术之python技术,看完学会再也不用去购买收费工具了-修改整体页面做好安卓下载发给客户-并且开始提交网站公安备案-作为APP下载落地页文娱产品一定要备案-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
    34
  • 4
    【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
    29
  • 5
    【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
    23
  • 6
    uniapp开发ios打包Error code = -5000 Error message: Error: certificate file(p12) import failed!报错问题如何解决
    143
  • 7
    【05】2025年1月首发完整版-篇幅较长-苹果app如何上架到app store完整流程·不借助第三方上架工具的情况下无需花钱但需仔细学习-优雅草央千澈详解关于APP签名以及分发-们最关心的一篇来了-IOS上架app
    235
  • 8
    app开发之安卓Android+苹果ios打包所有权限对应解释列表【长期更新】-以及默认打包自动添加权限列表和简化后的基本打包权限列表以uniapp为例-优雅草央千澈
    90
  • 9
    深入探索iOS开发中的SwiftUI框架
    145
  • 10
    ios样式开关按钮jQuery插件
    60