前言
最近需要开发app中的视频播放功能,首先要从系统相册中选择视频,然后将其在app中全屏播放。
一、工作背景
首先在unity中搭建了一个简单的平台,一个按钮和一个videoplayer物体。videoplayer使用的是unity自带的。
在button上挂了一个脚本,用来监听点击动作。通过判断不同的平台类型,安排对应的操作。
直接上代码 内联代码片
。
// unity端的代码
//定义相关的参数
public Button btn;
public GameObject m_videoPlayer;
//public Image ImageView;
AndroidJavaObject jo;
[DllImport("__Internal")]
private static extern void IOS_OpenAlbum();
//点击按钮
public void Start()
{
//点击按钮打开系统相册
Debug.Log("点击了打开相册按钮");
//在工程的开始阶段还是可以采用访问网页的形式上传视频的。
#if UNITY_EDITOR
Debug.Log("首先判断当前是unity编辑器,ios环境还是安卓环境,暂不做任何动作");
m_videoPlayer.GetComponentInChildren<PlayChoosedVideo>().urlNetWork = @"/Downloads/recording_2021_04_11_18_12_41_718.MP4";
AwakeAndCallUnity();
#elif UNITY_IOS
Debug.Log("当前是ios环境,将打开iphone中的相册");
AwakeAndCallIOS();
#elif UNITY_ANDROID
Debug.Log("当前是Android环境,将打开安卓或华为手机中的相册");
AwakeAndCallAndroid();
#endif
}
//平台是unity editor
private void AwakeAndCallUnity()
{
btn.onClick.AddListener(delegate {
m_videoPlayer.GetComponentInChildren<PlayChoosedVideo>().GoToPlayVideo();
});
}
//平台是ios则运行ios唤醒和召唤
private void AwakeAndCallIOS()
{
btn.onClick.AddListener(IOS_OpenAlbum);
}
//平台是android则运行安卓唤醒和召唤
private void AwakeAndCallAndroid()
{
AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
jo = jc.GetStatic<AndroidJavaObject>("currentActivity");
btn.onClick.AddListener(delegate {
CallAndroid();
Debug.Log("点击按钮");
});
}
void CallAndroid()
{
jo.Call("startPhoto");
Debug.Log("向安卓提出操作请求");
}
public void message(string str)
{
#if UNITY_ANDROID
Debug.Log("安卓传来信息:" + str);
StartCoroutine(LoadTexturePreview(str));
#elif UNITY_IOS
Debug.Log("苹果传来信息:" + str);
StartCoroutine(LoadTexturePreview(str));
#endif
}
IEnumerator LoadTexturePreview(string path)
{
yield return 0;
Debug.Log("视频地址:" + path);
//将该字符串地址传递给场景中的播放函数
m_videoPlayer.GetComponentInChildren<PlayChoosedVideo>().urlNetWork = path;
m_videoPlayer.GetComponentInChildren<PlayChoosedVideo>().GoToPlayVideo();
}
}
视频播放部分的代码,我挂在了videoplayer物体上。
下面展示一些 内联代码片
。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Video;
public class PlayChoosedVideo : MonoBehaviour
{
public GameObject m_VideoPLayer;
//public Slider sliderVolume;
//public Slider sliderVideo;
//是否拿到视频总时长
public bool isShow;
//时 分的转换
private int hour, min;
private float time;
private float time_Count;
private float time_Current;
private bool isVideo;
private VideoPlayer vPlayer;
//private AudioSource source;
public string urlNetWork;
public void GoToPlayVideo()
{
Init(urlNetWork);
}
private void Init(string url)
{
isVideo = true;
isShow = true;
time_Count = 0;
time_Current = 0;
//sliderVideo.value = 0;
vPlayer = m_VideoPLayer.GetComponentInChildren<VideoPlayer>();
//设置为URL模式
vPlayer.source = VideoSource.Url;
vPlayer.url = url;
//vPlayer.audioOutputMode = VideoAudioOutputMode.AudioSource;
//vPlayer.SetTargetAudioSource(0, source);
vPlayer.prepareCompleted += OnPrepared;
vPlayer.Prepare();
}
void OnPrepared(VideoPlayer player)
{
player.Play();
//sliderVideo.onValueChanged.AddListener(delegate { AdjustVideoProgress(sliderVideo.value); });
}
}
二、工作步骤
1.IOS部分
unity与ios交互的逻辑就是打开ios中的函数,传递代码,我们需要将ios的系统相册打开,选择其中的视频文件,然后返回给unity该视频文件的路径,然后在unity中将该路径的视频文件播放出来。
这里就需要在plugins/ios文件夹里增加ios的.h和.m文件,在生成的时候unity会自动将其发送给xcode。
.h的代码 内联代码片
。
#ifndef IOSCameraAlbumController_h
#define IOSCameraAlbumController_h
//import 引用头文件 相当于Using
#import<QuartzCore/CADisplayLink.h>
//声明一个IOSCameraController类 继承自UIViewController <>里面是是协议/代理的调用声明 可以理解为c#的接口
@interface
IOSCameraAlbumController : UIViewController<UIImagePickerControllerDelegate,UINavigationControllerDelegate>
@end
#endif /* IOSCameraAlbumController_h */
.m的代码 内联代码片
。
#import <Foundation/Foundation.h>
#import "IOSCameraAlbumController.h"
#import <MobileCoreServices/MobileCoreServices.h>
#import <Photos/Photos.h>
@implementation IOSCameraAlbumController
-(void)OpenTarget:(UIImagePickerControllerSourceType) type{
//创建UIImagePickerController实例
UIImagePickerController *picker;
picker = [[UIImagePickerController alloc] init];
//设置代理
picker.delegate = self;
//是否允许编辑 (默认为NO)
picker.allowsEditing = YES;
//设置照片的来源
// UIImagePickerControllerSourceTypePhotoLibrary, // 来自图库
// UIImagePickerControllerSourceTypeCamera, // 来自相机
// UIImagePickerControllerSourceTypeSavedPhotosAlbum // 来自相册
picker.sourceType = type;
picker.mediaTypes = @[@"public.movie"];
// picker.mediaTypes = [UIImagePickerController availableMediaTypesForSourceType:UIImagePickerControllerSourceTypePhotoLibrary];
[PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
//相册访问权限
if (status == PHAuthorizationStatusAuthorized) {
NSLog(@"Authorized");
}else{
NSLog(@"Denied or Restricted");
}
}];
//这里需要判断设备是iphone还是ipad 如果使用的是iphone并没有问题 但是如果 是在ipad上调用相册获取图片 会出现没有确定(选择)的按钮 所以这里判断
//了一下设备,针对ipad 使用另一种方法 但是这种方法是弹出一个界面 并不是覆盖整个界面 需要改进 试过另一种方式 重写一个相册界面
//(QQ的ipad选择头像的界面 就使用了这种方式 但是这里我们先不讲 (因为我也不太懂 但是我按照简书的一位老哥的文章写出来了 这里放一下这个简书的链接
//https://www.tlbyxzcx.com)
if (picker.sourceType == UIImagePickerControllerSourceTypePhotoLibrary &&[[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
// 设置弹出的控制器的显示样式
picker.modalPresentationStyle = UIModalPresentationPopover;
//获取这个弹出控制器
UIPopoverPresentationController *popover = picker.popoverPresentationController;
//设置代理
popover.delegate = self;
//下面两个属性设置弹出位置
popover.sourceRect = CGRectMake(0, 0, 0, 0);
popover.sourceView = self.view;
//设置箭头的位置
popover.permittedArrowDirections = UIPopoverArrowDirectionAny;
//展示选取照片控制器
[self presentViewController:picker animated:YES completion:nil];
} else {
//展示选取照片控制器
[self presentViewController:picker animated:YES completion:^{}];
}
}
//选择资源以后,获取资源路径
//控制器不会自己dismiss 需要我们手动在相应的地方实现
//这两个代理方法只会收到其中一个,取决于用户的点击情况
//结束采集之后 之后怎么处理都在这里写 通过Infokey取出相应的信息 Infokey可在进入文件中查看
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<UIImagePickerControllerInfoKey, id> *)info{
//查看是视频还是照片 public.image 或 public.movie
NSString * mediaType = [info objectForKey:UIImagePickerControllerMediaType];
if ([mediaType isEqualToString:@"public.image"]) {//照片
}
if ([mediaType isEqualToString:@"public.movie"]) {//视频
NSLog(@"found a video");
// 获取视频的名称
NSString *videoPath = [NSString stringWithFormat:@"%@",[info objectForKey:UIImagePickerControllerMediaURL]];
NSLog(@"%@", videoPath);
NSString *sendMessageToUnity = [[NSString alloc]init];
sendMessageToUnity = [NSString stringWithString:videoPath];
NSInteger leng = [sendMessageToUnity length];
NSLog(@"%ld",(long)leng);
const char *char_content = [sendMessageToUnity cStringUsingEncoding:NSASCIIStringEncoding];
UnitySendMessage("ClickOpenAlbum","message",char_content);
[picker dismissModalViewControllerAnimated:YES];
}
[self dismissViewControllerAnimated:picker completion:nil];
}
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker{
[self dismissViewControllerAnimated:picker completion:nil];
}
@end
//由于C++编译器需要支持函数的重载,会改变函数的名称,因此dll的导出函数通常是标准C定义的。
//这就使得C和C++的互相调用变得很常见。但是有时可能又会直接用C来调用,不想重新写代码,
//让标准C编写的dll函数定义在C和C++编译器下都能编译通过,通常会使用以下的格式:(这个格式在很多成熟的代码中很常见)
#if defined(__cplusplus)
extern "C" {
#endif
void IOS_OpenAlbum(){
IOSCameraAlbumController *app = [[IOSCameraAlbumController alloc]init];
UIViewController *vc = UnityGetGLViewController();
[vc.view addSubview:app.view];
[app OpenTarget:UIImagePickerControllerSourceTypePhotoLibrary];
}
#if defined(__cplusplus)
}
#endif
注意要引入photo框架,否则xcode生成的时候会报错。
2.Android部分
android需要在库里新建java包package,我选择新建了一个com.refreshalbum.callandroid的包,然后在这个包里建了名为CallAlbum.java的类,然后更改了AndroidManifest清单,让这个类可以通过unity呼唤,然后返回视频文件的路径。
直接上类的代码 内联代码片
。
package com.refreshalbum.callandroid;
import android.Manifest;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.MediaStore;
import android.util.Log;
import com.unity3d.player.UnityPlayer;
import com.unity3d.player.UnityPlayerActivity;
// 要注意这个地方继承 UnityPlayerActivity
public class CallAlbum extends UnityPlayerActivity{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 获取存储权限,不然的话无法获取图片
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE}, 100);
}
}
// unity点击按钮触发这个方法
public void startPhoto() {
Log.d("unity","打开相册");
Intent intent = new Intent(Intent.ACTION_PICK);
intent.setType("video/*");
startActivityForResult(intent, 111); // 第二个参数是请求码
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
switch (requestCode) {
case 111: // 请求码
Log.d("Unity", "相册返回");
UnityPlayer.UnitySendMessage("ClickOpenAlbum", "message", parseUri(data));
Log.d("unity", parseUri(data));
break;
}
}
}
public String parseUri(Intent data) {
Uri uri = data.getData();
String imagePath;
// 第二个参数是想要获取的数据
Cursor cursor = getContentResolver()
.query(uri, new String[]{MediaStore.Images.ImageColumns.DATA},
null, null, null);
if (cursor == null) {
imagePath = uri.getPath();
} else {
cursor.moveToFirst();
// 获取数据所在的列下标
int index = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
imagePath = cursor.getString(index); // 获取指定列的数据
cursor.close();
}
return imagePath; // 返回图片地址
}
}
3.导出打包测试
在build setting中export导出AS工程和Xcode工程进行测试,我这里没有报出什么错,直接成功生成。
总结
上述方法已经经过测试,完全有效,希望对其他小伙伴有所帮助。