549 lines
21 KiB
Swift
549 lines
21 KiB
Swift
//
|
||
// CCSpatialVideoDisplayController.swift
|
||
// SwiftProject
|
||
//
|
||
// Created by Zhang, Joyce on 2024/3/2.
|
||
//
|
||
|
||
import UIKit
|
||
import AVFoundation
|
||
|
||
|
||
|
||
enum SpatialType : Int {
|
||
|
||
/*
|
||
单眼2D
|
||
平行眼
|
||
红蓝立体
|
||
交叉眼
|
||
|
||
*/
|
||
|
||
case monocular2D
|
||
case parallelEyes
|
||
case redBlueSolid
|
||
case crossedEyes
|
||
}
|
||
|
||
class CCSpatialVideoDisplayController: BaseController {
|
||
|
||
|
||
var link = false//是否已连接设备
|
||
var isPlaying = false//是否正在播放
|
||
|
||
var selectedIndex:SpatialType = .monocular2D//记录当前选择的菜单选项
|
||
|
||
let convertor2 = VideoConvertor2()
|
||
|
||
var videoOriginalAsset:AVAsset?
|
||
var videoTempAsset:AVAsset?
|
||
|
||
//图片源数据
|
||
var sourceVideoURL:URL?
|
||
var outputVideoURL:URL?
|
||
|
||
|
||
var imgData:Data?
|
||
//空间视频 交叉眼 红蓝立体 平行眼 高斯模糊
|
||
var type = 0
|
||
|
||
var player:AVPlayer = AVPlayer()
|
||
|
||
|
||
lazy var mTopImgView:UIImageView = {
|
||
//393*236
|
||
let view = UIImageView(frame: CGRect(x: 0, y: 0, width: SCREEN_Width, height: SCREEN_Height * 236/393))
|
||
|
||
view.image = UIImage.init(named: "BG_Top")
|
||
return view
|
||
}()
|
||
|
||
lazy var transformButton: UIButton = {
|
||
|
||
//76*56
|
||
let transformButton = UIButton.init(type: UIButton.ButtonType.custom)
|
||
|
||
transformButton.tag = 201
|
||
transformButton.isSelected = false
|
||
transformButton.backgroundColor = UIColor(hexString: "#5326D6")
|
||
transformButton.addTarget(self, action: #selector(navgationButtonClick2(sender:)), for: UIControl.Event.touchUpInside)
|
||
let img2:UIImage = UIImage.init(named: "transform_button" as String)!
|
||
transformButton.setImage(img2, for: UIControl.State.normal)
|
||
transformButton.frame = CGRect(x: 0, y: 0, width: 56, height: 36)
|
||
transformButton.layer.cornerRadius = 18
|
||
transformButton.layer.masksToBounds = true
|
||
transformButton.centerY = StatuBar_Height + NavBar_Height * 0.5
|
||
transformButton.right = SCREEN_Width - 24
|
||
|
||
return transformButton
|
||
}()
|
||
|
||
lazy var mTopCenterTypeButton: UIButton = {
|
||
//173*36
|
||
let button = UIButton()
|
||
button.backgroundColor = UIColor.hexStringToColor(hexString: "#1F1E20")
|
||
button.tag = 202
|
||
button.isSelected = false
|
||
button.addTarget(self, action: #selector(navgationButtonClick2(sender:)), for: UIControl.Event.touchUpInside)
|
||
button.frame = CGRect(x: 2, y: 10, width: SCREEN_Width * 0.4, height: 36)
|
||
button.centerY = StatuBar_Height + NavBar_Height * 0.5
|
||
button.centerX = SCREEN_Width * 0.5
|
||
button.clipsToBounds = true
|
||
button.layer.cornerRadius = 18
|
||
button.layer.borderWidth = 1
|
||
button.layer.borderColor = UIColor.white.cgColor
|
||
button.setTitle("单眼2D", for: UIControl.State.normal)
|
||
button.setImage(UIImage.init(named: "type_button_arrow_down"), for: .normal)
|
||
button.updateBtnEdgeInsets(style: .Right, space: 10)
|
||
button.setTitleColor(UIColor.white, for: UIControl.State.normal)
|
||
button.titleLabel?.font = KFont_Medium(14)
|
||
return button
|
||
}()
|
||
|
||
lazy var playerLay:AVPlayerLayer = {
|
||
|
||
let view = AVPlayerLayer()
|
||
view.backgroundColor = UIColor.black.cgColor
|
||
view.frame = CGRect.init(x: 0, y: 250, width: self.view.frame.size.width, height: 240)
|
||
return view
|
||
}()
|
||
|
||
var typeData:[(icon:String,title:String,isHiden:Bool)] = [(icon:"type_check",title:"单眼2D",isHiden:false),
|
||
(icon:"type_check",title:"平行眼",isHiden:false),
|
||
(icon:"type_check",title:"红蓝立体",isHiden:false),
|
||
(icon:"type_check",title:"交叉眼",isHiden:false)]
|
||
|
||
lazy var menuView: CCSpatialDisplayTypeView = {
|
||
//数据源(icon可不填)
|
||
// let popData = [(icon:"type_check",title:"单眼2D",isHiden:false),
|
||
// (icon:"type_check",title:"平行眼",isHiden:false),
|
||
// (icon:"type_check",title:"红蓝立体",isHiden:false),
|
||
// (icon:"type_check",title:"交叉眼",isHiden:false)]
|
||
|
||
//设置参数
|
||
let parameters:[CCSpatialDisplayTypeConfigure] = [
|
||
.PopMenuTextColor(UIColor.white),
|
||
.popMenuItemHeight(40),
|
||
.PopMenuTextFont(KFont_Medium(12)),
|
||
.PopMenuBackgroudColor(UIColor(hexString: "#1F1E20"))
|
||
]
|
||
|
||
|
||
//init (test随机生成点位置,注意:arrow点是基于屏幕的位置)
|
||
let pointOnScreen = navtionImgView!.convert(CGPointMake(navtionImgView!.centerX, navtionImgView!.bottom), to: KWindow)
|
||
let popMenu = CCSpatialDisplayTypeView(menuWidth: SCREEN_Width * 0.4, arrow: pointOnScreen, datas: typeData,configures: parameters)
|
||
return popMenu
|
||
}()
|
||
|
||
lazy var progressView: UIProgressView = {
|
||
var view = UIProgressView (progressViewStyle:.default)
|
||
view.frame = CGRect(x: 24, y: SCREEN_Height - KStatusBarHeight - 20, width: SCREEN_Width-48, height: 36)
|
||
view.progress = 0.0 //默认进度50%
|
||
return view
|
||
}()
|
||
|
||
|
||
lazy var tipsButton: UIButton = {
|
||
|
||
//76*56
|
||
let button = UIButton.init(type: UIButton.ButtonType.custom)
|
||
button.tag = 203
|
||
button.backgroundColor = .clear
|
||
button.addTarget(self, action: #selector(navgationButtonClick2(sender:)), for: UIControl.Event.touchUpInside)
|
||
let img2:UIImage = UIImage.init(named: "tips_button" as String)!
|
||
button.setImage(img2, for: UIControl.State.normal)
|
||
button.frame = CGRect(x: 0, y: 0, width: 130, height: 30)
|
||
button.titleLabel?.font = KFont_Regular(14)
|
||
button.titleLabel?.adjustsFontSizeToFitWidth = true
|
||
button.updateBtnEdgeInsets(style: .Left, space: 8)
|
||
// button.layer.cornerRadius = 18
|
||
// button.layer.masksToBounds = true
|
||
button.centerY = progressView.top - 70
|
||
button.centerX = self.view.centerX
|
||
|
||
return button
|
||
}()
|
||
|
||
|
||
|
||
|
||
override func viewDidLoad() {
|
||
super.viewDidLoad()
|
||
|
||
self.view.backgroundColor = UIColor(hexString: "#060507")
|
||
// Do any additional setup after loading the view.
|
||
|
||
|
||
|
||
// let path = Bundle.main.path(forResource: "img3", ofType: "HEIC")
|
||
// photoOriginalURL = URL.init(filePath: path!)
|
||
outputVideoURL = URL.documentsDirectory.appending(path:"output11114.jpg")
|
||
|
||
//设置返回按钮图片
|
||
self.setLeftOneBtnImg(imgStr: "spatial_back_button")
|
||
// self.setLeftBtnImg(imgStr1: "", imgStr2: "spatial_back_button")
|
||
self.setNavgationBarColorImg(color: .clear)
|
||
self.setNavgationBarLine(color: .clear)
|
||
|
||
self.view.addSubview(mTopImgView)
|
||
self.view.bringSubviewToFront(self.navtionBar!)
|
||
// navtionBar?.addSubview(backButton)
|
||
navtionBar?.addSubview(transformButton)
|
||
navtionBar?.addSubview(mTopCenterTypeButton)
|
||
self.view.addSubview(progressView)
|
||
self.view.layer.addSublayer(playerLay)
|
||
self.view.addSubview(tipsButton)
|
||
|
||
|
||
|
||
if sourceVideoURL != nil {
|
||
outputVideoURL = URL.documentsDirectory.appending(path:"output1111.mp4")
|
||
videoOriginalAsset = AVAsset(url: sourceVideoURL!)
|
||
videoTempAsset = videoOriginalAsset
|
||
play()
|
||
}else{
|
||
print("这不是一张空间图片")
|
||
}
|
||
|
||
// 监听 AirPlay 设备的连接状态
|
||
NotificationCenter.default.addObserver(self, selector: #selector(airPlayStatusDidChange(_:)), name: AVAudioSession.routeChangeNotification, object: nil)
|
||
|
||
|
||
}
|
||
|
||
override func viewDidAppear(_ animated: Bool) {
|
||
super.viewDidAppear(animated)
|
||
// 检查当前是否已连接到 AirPlay 设备
|
||
checkAirPlayStatus()
|
||
}
|
||
|
||
//MARK: - 监听设备投流
|
||
@objc private func airPlayStatusDidChange(_ notification: Notification) {
|
||
checkAirPlayStatus()
|
||
}
|
||
|
||
private func checkAirPlayStatus() {
|
||
print("设备连接变化")
|
||
let currentRoute = AVAudioSession.sharedInstance().currentRoute
|
||
let isAirPlayActive = currentRoute.outputs.contains { output in
|
||
return output.portType == AVAudioSession.Port.HDMI ||
|
||
output.portType == AVAudioSession.Port.airPlay
|
||
}
|
||
|
||
setttinisScreenMirroring(isScreenMirroring: isAirPlayActive)
|
||
}
|
||
|
||
func setttinisScreenMirroring(isScreenMirroring:Bool){
|
||
|
||
//已连接
|
||
if(isScreenMirroring){
|
||
print("已连接")
|
||
link = true
|
||
isPlaying = true
|
||
playerLay.player!.usesExternalPlaybackWhileExternalScreenIsActive = true
|
||
playerLay.player!.allowsExternalPlayback = true
|
||
|
||
//串流播放中
|
||
// mTopCenterTypeButton.setTitle("串流播放中", for: UIControl.State.normal)
|
||
|
||
mTopCenterTypeButton.setImage(UIImage.init(named: "linked_button"), for: .normal)
|
||
mTopCenterTypeButton.setTitleColor(UIColor(hexString: "#D0C0FF"), for: .normal)
|
||
mTopCenterTypeButton.layer.borderColor = UIColor(hexString: "#D0C0FF").cgColor
|
||
mTopCenterTypeButton.backgroundColor = UIColor(hexString: "#5326D6")
|
||
mTopCenterTypeButton.updateBtnEdgeInsets(style: .Left, space: 5)
|
||
|
||
playerLay.player!.play()
|
||
|
||
|
||
//展示弹出列表尾部
|
||
if isPlaying {
|
||
//正在串流中 --- 按钮显示为【结束串流】
|
||
menuView.showFooterView(isShow: true, showText: "结束串流")
|
||
mTopCenterTypeButton.setTitle("外部串流中", for: UIControl.State.normal)
|
||
}else{
|
||
//未串流 --- 按钮显示为【开始串流】
|
||
menuView.showFooterView(isShow: true, showText: "开始串流")
|
||
mTopCenterTypeButton.setTitle("已连接外部设备", for: UIControl.State.normal)
|
||
}
|
||
|
||
|
||
}else{
|
||
//未连接
|
||
print("未连接")
|
||
link = false
|
||
isPlaying = false
|
||
// 当前未连接到 AirPlay 设备
|
||
playerLay.player!.usesExternalPlaybackWhileExternalScreenIsActive = false
|
||
playerLay.player!.allowsExternalPlayback = false
|
||
|
||
if self.selectedIndex == .monocular2D {
|
||
mTopCenterTypeButton.setTitle("单眼2D", for: UIControl.State.normal)
|
||
}else if self.selectedIndex == .redBlueSolid {
|
||
mTopCenterTypeButton.setTitle("红蓝立体", for: UIControl.State.normal)
|
||
}else if self.selectedIndex == .crossedEyes {
|
||
mTopCenterTypeButton.setTitle("交叉眼", for: UIControl.State.normal)
|
||
}
|
||
else if self.selectedIndex == .parallelEyes {
|
||
mTopCenterTypeButton.setTitle("平行眼", for: UIControl.State.normal)
|
||
}
|
||
|
||
mTopCenterTypeButton.setImage(UIImage.init(named: "type_button_arrow_down"), for: .normal)
|
||
mTopCenterTypeButton.setTitleColor(UIColor.white, for: UIControl.State.normal)
|
||
mTopCenterTypeButton.layer.borderColor = UIColor.white.cgColor
|
||
mTopCenterTypeButton.backgroundColor = UIColor(hexString: "#1F1E20")
|
||
mTopCenterTypeButton.updateBtnEdgeInsets(style: .Right, space: 10)
|
||
|
||
//隐藏弹出列表尾部
|
||
menuView.showFooterView(isShow: false, showText: "")
|
||
|
||
}
|
||
}
|
||
|
||
deinit {
|
||
NotificationCenter.default.removeObserver(self)
|
||
}
|
||
|
||
|
||
//播放
|
||
func play(){
|
||
|
||
let playerItem = AVPlayerItem(asset: videoTempAsset!)
|
||
playerLay.player = AVPlayer(playerItem: playerItem)
|
||
playerLay.player!.play()
|
||
}
|
||
|
||
|
||
//MARK: - action
|
||
@objc public func navgationButtonClick2(sender:UIButton){
|
||
|
||
if sender.tag == 200 {
|
||
//左边按钮
|
||
}else if sender.tag == 201 {
|
||
//右边按钮
|
||
let transVC = VRVideoTransformController()
|
||
transVC.sourceVideoURL = sourceVideoURL
|
||
self.navigationController?.pushViewController(transVC, animated: true)
|
||
}else if sender.tag == 202 {
|
||
//中间按钮
|
||
|
||
menuView.show()
|
||
if link {
|
||
//展示弹出列表尾部
|
||
if isPlaying {
|
||
//正在串流中 --- 按钮显示为【结束串流】
|
||
menuView.showFooterView(isShow: true, showText: "结束串流")
|
||
mTopCenterTypeButton.setTitle("外部串流中", for: UIControl.State.normal)
|
||
}else{
|
||
//未串流 --- 按钮显示为【开始串流】
|
||
menuView.showFooterView(isShow: true, showText: "开始串流")
|
||
mTopCenterTypeButton.setTitle("已连接外部设备", for: UIControl.State.normal)
|
||
}
|
||
}else{
|
||
mTopCenterTypeButton.setImage(UIImage.init(named: "type_button_arrow_up"), for: .normal)
|
||
}
|
||
|
||
|
||
//click
|
||
menuView.didSelectMenuBlock = { [weak self](index:Int)->Void in
|
||
print("block select \(index)")
|
||
self?.mTopCenterTypeButton.setImage(UIImage.init(named: "type_button_arrow_down"), for: .normal)
|
||
self?.selectedSpatialType(selectedIndex: index)
|
||
}
|
||
|
||
//tap
|
||
menuView.tapFooterActionBlock = {
|
||
self.startOrEndExternalVR()
|
||
}
|
||
|
||
}else if sender.tag == 203 {
|
||
//tips
|
||
let view = CCDeviceOperationListView.init(frame: CGRectMake(0, 0, KScreenWidth, KScreenHeight))
|
||
KWindow?.addSubview(view)
|
||
}
|
||
}
|
||
|
||
func startOrEndExternalVR() {
|
||
//开始串流/结束串流
|
||
if(link == true){
|
||
isPlaying = !isPlaying
|
||
if(isPlaying == true){
|
||
// 当前已连接到 AirPlay 设备
|
||
playerLay.player!.usesExternalPlaybackWhileExternalScreenIsActive = true
|
||
playerLay.player!.allowsExternalPlayback = true
|
||
}else{
|
||
playerLay.player!.usesExternalPlaybackWhileExternalScreenIsActive = false
|
||
playerLay.player!.allowsExternalPlayback = false
|
||
|
||
}
|
||
}
|
||
}
|
||
|
||
func selectedSpatialType(selectedIndex:Int) {
|
||
|
||
// if selectedIndex == 0 {
|
||
// self.selectedIndex = .monocular2D
|
||
// }else if selectedIndex == 1 {
|
||
// self.selectedIndex = .redBlueSolid
|
||
// }else if selectedIndex == 2 {
|
||
// self.selectedIndex = .crossedEyes
|
||
// }
|
||
|
||
self.selectedIndex = SpatialType(rawValue: selectedIndex) ?? .monocular2D
|
||
|
||
player.pause()
|
||
NotificationCenter.default.removeObserver(self)
|
||
|
||
//立体视频
|
||
if(selectedIndex == 0){
|
||
videoTempAsset = videoOriginalAsset
|
||
play()
|
||
}
|
||
else{
|
||
outputVideoURL = URL.documentsDirectory.appending(path:"output11112.mp4")
|
||
}
|
||
|
||
//红蓝立体
|
||
if(self.selectedIndex == .redBlueSolid){
|
||
Task {
|
||
convertor2.type = 3
|
||
|
||
try await convertor2.convertVideo(asset: videoOriginalAsset!, outputFile: outputVideoURL! ) { [self] progress in
|
||
print(progress)
|
||
DispatchQueue.main.async { [weak self] in
|
||
self?.progressView.setProgress(progress, animated: true)
|
||
if(progress > 0.99){
|
||
self!.videoTempAsset = AVAsset(url: self!.outputVideoURL!)
|
||
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
|
||
// 要执行的任务
|
||
self!.play()
|
||
}
|
||
|
||
}
|
||
}
|
||
|
||
}
|
||
}
|
||
}
|
||
|
||
//交叉眼
|
||
if(self.selectedIndex == .crossedEyes){
|
||
Task {
|
||
convertor2.type = 2
|
||
|
||
try await convertor2.convertVideo(asset: videoOriginalAsset!, outputFile: outputVideoURL! ) { [self] progress in
|
||
print(progress)
|
||
DispatchQueue.main.async { [weak self] in
|
||
self?.progressView.setProgress(progress, animated: true)
|
||
if(progress > 0.99){
|
||
self!.videoTempAsset = AVAsset(url: self!.outputVideoURL!)
|
||
self!.play()
|
||
}
|
||
}
|
||
|
||
}
|
||
}
|
||
}
|
||
|
||
//平行眼
|
||
if(self.selectedIndex == .crossedEyes){
|
||
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
//空间视频
|
||
// if(selectedIndex == 1){
|
||
//// Task {
|
||
//// try await videoConverter.convertStereoscopicVideoToSpatialVideo(sourceVideoURL: sourceVideoURL!,outputVideoURL: outputVideoURL!){[weak self] progress in
|
||
//// print(progress)
|
||
//// DispatchQueue.main.async { [weak self] in
|
||
//// self!.btn3!.setTitle("进度=" + String(progress), for: UIControl.State.normal)
|
||
//// if(progress > 0.99){
|
||
//// self!.videoTempAsset = AVAsset(url: self!.outputVideoURL!)
|
||
//// self!.play()
|
||
//// }
|
||
//// }
|
||
//// }
|
||
//// }
|
||
// }
|
||
|
||
//高斯模糊
|
||
// if(selectedIndex == 4){
|
||
// Task {
|
||
// convertor2.type = 4
|
||
//
|
||
// try await convertor2.convertVideo(asset: videoTempAsset!, outputFile: outputVideoURL! ) { [self] progress in
|
||
// print(progress)
|
||
// DispatchQueue.main.async { [weak self] in
|
||
// self?.progressView.setProgress(progress, animated: true)
|
||
// if(progress > 0.99){
|
||
// self!.videoTempAsset = AVAsset(url: self!.outputVideoURL!)
|
||
// self!.play()
|
||
// }
|
||
// }
|
||
//
|
||
// }
|
||
// }
|
||
// }
|
||
}
|
||
|
||
|
||
|
||
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
|
||
|
||
|
||
if let mediaType = info[UIImagePickerController.InfoKey.mediaType] as? String, mediaType == "public.movie" {
|
||
let videoURL = info[.mediaURL] as? URL
|
||
print("Selected video URL: \(videoURL)")
|
||
sourceVideoURL = videoURL
|
||
videoOriginalAsset = AVAsset(url: sourceVideoURL!)
|
||
videoTempAsset = videoOriginalAsset
|
||
if(!isSpatialVideo(asset: videoTempAsset!)){
|
||
showTextAlert(title: "提示", message: "当前视频不是空间视频")
|
||
}
|
||
play()
|
||
}
|
||
|
||
dismiss(animated: true, completion: nil)
|
||
}
|
||
|
||
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
|
||
dismiss(animated: true, completion: nil)
|
||
}
|
||
|
||
//检查是否为空间视频
|
||
func isSpatialVideo(asset: AVAsset) -> Bool {
|
||
let metadata = asset.metadata(forFormat: AVMetadataFormat.quickTimeMetadata)
|
||
let isSpatialVideo = metadata.contains { item in
|
||
if let identifier = item.identifier?.rawValue {
|
||
return identifier == "mdta/com.apple.quicktime.spatial.format-version"
|
||
}
|
||
return false
|
||
}
|
||
return isSpatialVideo
|
||
}
|
||
|
||
func showTextAlert(title: String, message: String) {
|
||
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
|
||
let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
|
||
alertController.addAction(okAction)
|
||
|
||
// 在视图控制器中显示弹窗
|
||
present(alertController, animated: true, completion: nil)
|
||
}
|
||
|
||
/*
|
||
// MARK: - Navigation
|
||
|
||
// In a storyboard-based application, you will often want to do a little preparation before navigation
|
||
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||
// Get the new view controller using segue.destination.
|
||
// Pass the selected object to the new view controller.
|
||
}
|
||
*/
|
||
|
||
}
|