- 作者:老汪软件技巧
- 发表时间:2024-09-16 04:01
- 浏览量:
一、背景
在APP上实现如图功能, 用户下载视频,然后用户授权保存在相册中, 下载时loading,成功失败分别进行提示
二、ios 端视频下载2-1、h5端发送下载视频消息,传入视频地址
iosBridge.pubSub.publish(messageKey.sendToNative, messageKey.downLoadVideo, {
videoUrl: videoURL,
});
2-2、ios 端接收消息,执行下载方法1、视频下载
Ios 端通过封装 VideoDownloader 实例,传入videourl 当下载成功或者失败,通过执行回调函数进行后续操作,后续主要是通过消息通知h5
-(void)downLoadVideo:(NSString *)videoUrl{
// 开始下载
[self channelMessage:@"showLoading" withData: @"true"];
NSLog(@"videoUrl%@",videoUrl);
// 创建下载器实例
VideoDownloader *downloader = [[VideoDownloader alloc] init];
// 要下载的URL
NSURL *videoURL = [NSURL URLWithString:videoUrl];
// 开始下载
[downloader downloadVideoWithURL:videoURL completion:^(NSURL *filePath, NSError *error) {
if (error) {
NSLog(@"视频下载失败: %@", error.localizedDescription);
[self channelMessage:@"showLoading" withData: @"false"];
[self channelMessage:@"showToast" withData: @"Download failed"];
} else {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self saveVideoToPhotoLibrary:filePath.path];
});
}
}];
}
2、保存到相册
下载成功后会获取到视频的下载地址 filePath.path 然后通过 【PHPhotoLibrary】 将视频地址路径保存到相册中
//保存到相册
- (void)saveVideoToPhotoLibrary:(NSString *)filePath {
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
[PHAssetChangeRequest creationRequestForAssetFromVideoAtFileURL:[NSURL fileURLWithPath:filePath]];
} completionHandler:^(BOOL success, NSError * _Nullable error) {
if (success) {
NSLog(@"视频已保存到相册");
[self channelMessage:@"showLoading" withData: @"false"];
[self channelMessage:@"showToast" withData: @"The video has been saved to the photo album"];
} else {
[self channelMessage:@"showLoading" withData: @"false"];
NSLog(@"保存视频到相册失败: %@", error.localizedDescription);
[self channelMessage:@"showToast" withData: @"Failed to save the video to the photo album"];
}
}];
}
3、实现 VideoDownloader
暴露 downloadVideoWithURL 方法,入参为 videoUrl 和 completion 函数,执行后续结果
VideoDownloader.h
#import
NS_ASSUME_NONNULL_BEGIN
@interface VideoDownloader : NSObject
- (void)downloadVideoWithURL:(NSURL *)url
completion:(void (^)(NSURL *filePath, NSError *error))completion;
@end
NS_ASSUME_NONNULL_END
VideoDownloader.m
#import "VideoDownloader.h"
@implementation VideoDownloader
- (void)downloadVideoWithURL:(NSURL *)url
completion:(void (^)(NSURL *filePath, NSError *error))completion {
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithURL:url
completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
if (error) {
completion(nil, error);
return;
}
// 将下载的文件移到临时目录
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *documentsDirectory = [[fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] firstObject];
NSURL *destinationURL = [documentsDirectory URLByAppendingPathComponent:[response suggestedFilename]];
// 删除已存在的文件
if ([fileManager fileExistsAtPath:[destinationURL path]]) {
[fileManager removeItemAtURL:destinationURL error:nil];
}
NSError *fileError;
[fileManager moveItemAtURL:location toURL:destinationURL error:&fileError];
if (fileError) {
completion(nil, fileError);
} else {
completion(destinationURL, nil);
}
}];
[downloadTask resume];
}
@end
通过NSURLSessionDownloadTask 执行下载任务
三、android 端视频下载3-1、h5发送下载视频消息,传入视频地址
androidBridge.pubSub.publish(androidBridgeMessage.sendToNative,androidBridgeMessage.downLoadVideo, {
videoUrl: videoURL
});
3-2、android 端接收消息,执行下载任务
执行 downLoadVideo 方法传入 videoUrl 参数,同样将业务逻辑封装到 DownloadUtil 类中, 在执行时通知 h5 loading, 下载成功或者失败后执行相应的回调函数
@JavascriptInterface
fun downLoadVideo(videoJSON:String) {
val jsonObj = JSONObject(videoJSON)
val videoUrl = jsonObj.getString("videoUrl")
callJsFromAndroid("channelMessage","showLoading", "true")
Handler().postDelayed({
DownloadUtil.downloadVideo(this@MainActivity, videoUrl) { success, message ->
if (success) {
callJsFromAndroid("channelMessage","showToast", message)
} else {
callJsFromAndroid("channelMessage","showToast", message)
}
}
},1000)
}
这里为了防止下载太快,增加了1秒的延时, 为了更好的展示loading
3-3、实现 DownloadUtil.kt 类
DownloadUtil 类中主要通过 DownloadManager 实现videoUrl 的下载,当下载成功后,通知媒体扫描器,已便相册可以扫描到新下载的视频, 通过 DownloadManager.Query().setFilterById 循环检查是否下载成功, 这里通过 handler.postDelayed 优化检查过程, 减少查询频次,否则在下载大视频时,一些安卓机型容易崩溃导致退出,代码如下
class DownloadUtil {
companion object {
private val handler = Handler(Looper.getMainLooper())
private val VIDEO_ID = "7408881885343976710"
fun downloadVideo(
context: Context,
videoUrl: String,
onDownloadComplete: (Boolean, String?) -> Unit
) {
val downloadManager =
context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
val request = DownloadManager.Request(Uri.parse(videoUrl))
.setTitle("Downloading video")
.setDescription("Downloading video file")
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
.setDestinationInExternalPublicDir(
Environment.DIRECTORY_MOVIES,
"video_$VIDEO_ID.mp4"
)
val downloadId = downloadManager.enqueue(request)
queryDownloadStatus(context, downloadId, object : DownloadStatusListener {
override fun onStatusRetrieved(status: Int) {
Log.d("videoFile lll", "onStatusRetrievedkkk:")
when (status) {
DownloadManager.STATUS_FAILED -> {
onDownloadComplete(
false,
"Failed to save the video to the photo album"
)
}
DownloadManager.STATUS_SUCCESSFUL -> {
// 通知媒体扫描器,以便相册可以扫描到新下载的视频
val videoFile =
File(context.getExternalFilesDir(null), "video_$VIDEO_ID.mp4")
MediaScannerConnection.scanFile(
context,
arrayOf(videoFile.absolutePath),
null,
null
)
onDownloadComplete(
true,
"The video has been saved to the photo album"
)
}
else -> {
// 继续检查
handler.postDelayed(
{ queryDownloadStatus(context, downloadId, this) },
1000
)
}
}
}
})
}
}
}
private fun queryDownloadStatus(
context: Context,
downloadId: Long,
listener: DownloadStatusListener,
) {
val query = DownloadManager.Query().setFilterById(downloadId)
val downloadManager =
context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
downloadManager.query(query).use { cursor ->
if (cursor.moveToFirst()) {
val statusIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)
val status = cursor.getInt(statusIndex)
listener.onStatusRetrieved(status)
}
}
}
interface DownloadStatusListener {
fun onStatusRetrieved(status: Int)
}
三、 总结
在app上实现视频下载,思路还是比较清晰的, 如果需要权限,就检查权限,下载视频后,保存到用户相册中, 方便用户查看, ios 上在开发时遇到的问题不多, 主要是用户授权的问题, 当用户授权允许保存到相册时,执行相应的方法, 在android 上开发比较坑的时, 安卓对权限这块版本的变化很快, 开始下载视频不成功,一直以为是权限问题, 在android 低版本确实是需要提示用户请求对应权限,才有写入相册的权限,但是后面高版本逐渐废弃了,可以不通过用户授权也可以下载视频到内部地址,在通过媒体扫描更新相册,用户即可看到下载后的视频内容,由于不是专业端开发人员, 里面还是有很多地方需要补充完善, 比如ios用户拒绝授权,下次点击应该检查权限,再次调用授权弹窗,比如android对于低版本的用户的兼容处理,等等应该还有很多细节, 目前还是先实现功能为主,特此记录