记录unity视频选择与播放_IOS&Android


前言

最近需要开发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工程进行测试,我这里没有报出什么错,直接成功生成。

总结

上述方法已经经过测试,完全有效,希望对其他小伙伴有所帮助。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值