Research on Karaoke
This commit is contained in:
@@ -61,6 +61,8 @@
|
||||
5259545A2BEB67D200191286 /* DateFormatterLib.swift in Sources */ = {isa = PBXBuildFile; fileRef = 525954592BEB67D200191286 /* DateFormatterLib.swift */; };
|
||||
5259545C2BEBB80400191286 /* AvatarDM.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5259545B2BEBB80400191286 /* AvatarDM.swift */; };
|
||||
5259545E2BEBBA1A00191286 /* LoadingIndicatorImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5259545D2BEBBA1A00191286 /* LoadingIndicatorImageView.swift */; };
|
||||
525FC61D2C3D3DC30049145D /* AVAssetMods.swift in Sources */ = {isa = PBXBuildFile; fileRef = 525FC61C2C3D3DC30049145D /* AVAssetMods.swift */; };
|
||||
525FC65D2C3D57D80049145D /* TestingKaraokeVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 525FC65C2C3D57D80049145D /* TestingKaraokeVC.swift */; };
|
||||
525FD79B2C2AFB990062C80F /* JWPlayerKit in Frameworks */ = {isa = PBXBuildFile; productRef = 525FD79A2C2AFB990062C80F /* JWPlayerKit */; };
|
||||
52663FF52BDFAB830001D8CE /* TextFieldErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52663FF42BDFAB830001D8CE /* TextFieldErrorView.swift */; };
|
||||
52663FF72BDFACF60001D8CE /* ShadowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52663FF62BDFACF60001D8CE /* ShadowView.swift */; };
|
||||
@@ -169,6 +171,7 @@
|
||||
52DAC6482C21762900E2F85B /* WebSeries.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 52DAC6472C21762900E2F85B /* WebSeries.storyboard */; };
|
||||
52DAC64E2C21775300E2F85B /* WebSeriesVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52DAC64D2C21775300E2F85B /* WebSeriesVC.swift */; };
|
||||
52E214C72C2AD47F00BC2D29 /* EpisodeDetailsVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52E214C62C2AD47F00BC2D29 /* EpisodeDetailsVC.swift */; };
|
||||
52F4E8662C3D123B00778FBC /* JWKaraokePlayerVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52F4E8652C3D123B00778FBC /* JWKaraokePlayerVC.swift */; };
|
||||
52FB2D8F2BDF898F0009B0C7 /* TextFieldPadding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52FB2D8E2BDF898F0009B0C7 /* TextFieldPadding.swift */; };
|
||||
52FDBA782BFF23F4009D7AC7 /* TimePeriod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52FDBA772BFF23F4009D7AC7 /* TimePeriod.swift */; };
|
||||
52FDBA7B2BFF2712009D7AC7 /* AuthFuncTimeHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52FDBA7A2BFF2712009D7AC7 /* AuthFuncTimeHandling.swift */; };
|
||||
@@ -340,6 +343,8 @@
|
||||
525954592BEB67D200191286 /* DateFormatterLib.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateFormatterLib.swift; sourceTree = "<group>"; };
|
||||
5259545B2BEBB80400191286 /* AvatarDM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarDM.swift; sourceTree = "<group>"; };
|
||||
5259545D2BEBBA1A00191286 /* LoadingIndicatorImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingIndicatorImageView.swift; sourceTree = "<group>"; };
|
||||
525FC61C2C3D3DC30049145D /* AVAssetMods.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AVAssetMods.swift; sourceTree = "<group>"; };
|
||||
525FC65C2C3D57D80049145D /* TestingKaraokeVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestingKaraokeVC.swift; sourceTree = "<group>"; };
|
||||
52663FF42BDFAB830001D8CE /* TextFieldErrorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldErrorView.swift; sourceTree = "<group>"; };
|
||||
52663FF62BDFACF60001D8CE /* ShadowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShadowView.swift; sourceTree = "<group>"; };
|
||||
52663FF82BDFAF110001D8CE /* EmailVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailVM.swift; sourceTree = "<group>"; };
|
||||
@@ -451,6 +456,7 @@
|
||||
52E214C62C2AD47F00BC2D29 /* EpisodeDetailsVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EpisodeDetailsVC.swift; sourceTree = "<group>"; };
|
||||
52E7E0F62BDF7DD500C86E10 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/AuthenticationSB.strings; sourceTree = "<group>"; };
|
||||
52E7E0F82BDF7DD900C86E10 /* hi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hi; path = hi.lproj/AuthenticationSB.strings; sourceTree = "<group>"; };
|
||||
52F4E8652C3D123B00778FBC /* JWKaraokePlayerVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JWKaraokePlayerVC.swift; sourceTree = "<group>"; };
|
||||
52FB2D8E2BDF898F0009B0C7 /* TextFieldPadding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldPadding.swift; sourceTree = "<group>"; };
|
||||
52FDBA772BFF23F4009D7AC7 /* TimePeriod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimePeriod.swift; sourceTree = "<group>"; };
|
||||
52FDBA7A2BFF2712009D7AC7 /* AuthFuncTimeHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthFuncTimeHandling.swift; sourceTree = "<group>"; };
|
||||
@@ -1382,6 +1388,9 @@
|
||||
9CB3D08A2C37BBA50062869D /* KaraokeListingVC.swift */,
|
||||
9CB3D0902C37D6930062869D /* KaraokeDetailsVC.swift */,
|
||||
9C21F81D2C37E3CA0050BFCC /* AVPlayerVC.swift */,
|
||||
52F4E8652C3D123B00778FBC /* JWKaraokePlayerVC.swift */,
|
||||
525FC61C2C3D3DC30049145D /* AVAssetMods.swift */,
|
||||
525FC65C2C3D57D80049145D /* TestingKaraokeVC.swift */,
|
||||
);
|
||||
path = Controller;
|
||||
sourceTree = "<group>";
|
||||
@@ -1708,12 +1717,14 @@
|
||||
52D6A2512C22B58200145908 /* WebSeriesShowListingCell.swift in Sources */,
|
||||
9C007F232C25603800F798C2 /* WebSeriesEpisodeCell.swift in Sources */,
|
||||
5272FCE32BDFDB05000ECB1D /* UserDetailsRegisterVC.swift in Sources */,
|
||||
525FC65D2C3D57D80049145D /* TestingKaraokeVC.swift in Sources */,
|
||||
525954102BE8B72900191286 /* FontCustom.swift in Sources */,
|
||||
5202AAFE2BDF90590043B7BD /* TextFieldImage.swift in Sources */,
|
||||
9C7939152C0F23AA00F5D6E6 /* NsNotificationExtension.swift in Sources */,
|
||||
528E5F1B2C24531200E33E4E /* SeasonListingDM.swift in Sources */,
|
||||
52FDDAB52BF34DC300E037C1 /* YesNoAlertVC.swift in Sources */,
|
||||
52C6E0232BE3B3E300E22D59 /* SelectAvatarVC.swift in Sources */,
|
||||
52F4E8662C3D123B00778FBC /* JWKaraokePlayerVC.swift in Sources */,
|
||||
529B0DD62C070C0F00CFC54B /* GuestDataDM.swift in Sources */,
|
||||
5259545C2BEBB80400191286 /* AvatarDM.swift in Sources */,
|
||||
52C8B06C2BDA6E87003B51D0 /* LocalizedString.swift in Sources */,
|
||||
@@ -1820,6 +1831,7 @@
|
||||
52C8EC7D2C3536E5002DC35C /* ContinueAudioCell.swift in Sources */,
|
||||
525954252BE8F01600191286 /* ValueWrapper.swift in Sources */,
|
||||
52A3F6A82BECBF2A0000BB0B /* LinkedChildCell.swift in Sources */,
|
||||
525FC61D2C3D3DC30049145D /* AVAssetMods.swift in Sources */,
|
||||
52BC3BEE2C16FBDB002FACA6 /* MoreVC.swift in Sources */,
|
||||
52C6E01E2BE3847F00E22D59 /* BorderView.swift in Sources */,
|
||||
52FDBA7D2BFF481A009D7AC7 /* ThemeOneVM.swift in Sources */,
|
||||
|
||||
@@ -86,7 +86,7 @@ extension K{
|
||||
struct Karaoke{
|
||||
static let karaokeListingVC = "KaraokeListingVC"
|
||||
static let karaokeDetailsVC = "KaraokeDetailsVC"
|
||||
static let aVPlayerVC = "AVPlayerVC"
|
||||
static let jwKaraokePlayerVC = "JWKaraokePlayerVC"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>Give Permissions for Karaoke</string>
|
||||
<key>API_KEY_ID</key>
|
||||
<string>$(API_KEY_ID)</string>
|
||||
<key>API_KEY_PASS</key>
|
||||
|
||||
110
WOKA/Karaoke/Controller/AVAssetMods.swift
Normal file
110
WOKA/Karaoke/Controller/AVAssetMods.swift
Normal file
@@ -0,0 +1,110 @@
|
||||
//
|
||||
// AVAssetMods.swift
|
||||
// WOKA
|
||||
//
|
||||
// Created by MacBook Pro on 09/07/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import AVKit
|
||||
|
||||
extension AVAsset {
|
||||
|
||||
func writeAudioTrackToURL(_ url: URL, completion: @escaping (Bool, Error?, URL?) -> ()) {
|
||||
do {
|
||||
let audioAsset = try self.audioAsset()
|
||||
audioAsset.writeToURL(url, completion: completion)
|
||||
} catch (let error as NSError){
|
||||
completion(false, error, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func writeToURL(_ url: URL, completion: @escaping (Bool, Error?, URL?) -> ()) {
|
||||
guard let exportSession = AVAssetExportSession(asset: self, presetName: AVAssetExportPresetAppleM4A) else {
|
||||
completion(false, nil , nil)
|
||||
return
|
||||
}
|
||||
|
||||
Utilities.startProgressHUD(msg: "Preparing")
|
||||
// Create an AVMutableAudioMix to adjust the volume
|
||||
let audioMix = AVMutableAudioMix()
|
||||
var inputParameters = [AVMutableAudioMixInputParameters]()
|
||||
|
||||
// Decrease the volume by setting the volume to a value less than 1.0
|
||||
let volume : Float = 0.4 // Adjust the volume level as needed (e.g., 0.5 for half volume)
|
||||
|
||||
// Create an AVMutableAudioMixInputParameters instance for each audio track
|
||||
for track in self.tracks(withMediaType: .audio) {
|
||||
let audioInputParams = AVMutableAudioMixInputParameters(track: track)
|
||||
audioInputParams.setVolume(volume, at: .zero) // Set the volume for the audio track
|
||||
inputParameters.append(audioInputParams)
|
||||
}
|
||||
|
||||
// Assign the input parameters to the audio mix
|
||||
audioMix.inputParameters = inputParameters
|
||||
|
||||
// Set the audio mix for the export session
|
||||
exportSession.audioMix = audioMix
|
||||
|
||||
// Configure export session and start exporting
|
||||
exportSession.outputFileType = .m4a
|
||||
exportSession.outputURL = url
|
||||
|
||||
exportSession.exportAsynchronously {
|
||||
switch exportSession.status {
|
||||
case .completed:
|
||||
Utilities.dismissProgressHUD()
|
||||
completion(true, nil, url)
|
||||
case .unknown, .waiting, .exporting, .failed, .cancelled:
|
||||
Utilities.dismissProgressHUD()
|
||||
completion(false, nil, nil)
|
||||
}
|
||||
}
|
||||
// guard let exportSession = AVAssetExportSession(asset: self, presetName: AVAssetExportPresetAppleM4A) else {
|
||||
// completion(false, nil , nil)
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// exportSession.outputFileType = .m4a
|
||||
// exportSession.outputURL = url
|
||||
//
|
||||
// exportSession.exportAsynchronously {
|
||||
// switch exportSession.status {
|
||||
// case .completed:
|
||||
// completion(true, nil, url)
|
||||
// case .unknown, .waiting, .exporting, .failed, .cancelled:
|
||||
// completion(false, nil, nil)
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
func audioAsset() throws -> AVAsset {
|
||||
|
||||
let composition = AVMutableComposition()
|
||||
let audioTracks = tracks(withMediaType: .audio)
|
||||
|
||||
for track in audioTracks {
|
||||
|
||||
let compositionTrack = composition.addMutableTrack(withMediaType: .audio, preferredTrackID: kCMPersistentTrackID_Invalid)
|
||||
try compositionTrack?.insertTimeRange(track.timeRange, of: track, at: track.timeRange.start)
|
||||
compositionTrack?.preferredTransform = track.preferredTransform
|
||||
}
|
||||
return composition
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension FileManager {
|
||||
func clearTmpDirectory() {
|
||||
do {
|
||||
let tmpDirURL = FileManager.default.temporaryDirectory
|
||||
let tmpDirectory = try contentsOfDirectory(atPath: tmpDirURL.path)
|
||||
try tmpDirectory.forEach { file in
|
||||
let fileUrl = tmpDirURL.appendingPathComponent(file)
|
||||
try removeItem(atPath: fileUrl.path)
|
||||
}
|
||||
} catch {
|
||||
//catch the error somehow
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,9 +9,9 @@ import UIKit
|
||||
import AVFoundation
|
||||
|
||||
class AVPlayerVC: UIViewController {
|
||||
|
||||
|
||||
@IBOutlet weak var videoPlayer: UIView!
|
||||
// @IBOutlet weak var videoPlayerHeight: NSLayoutConstraint!
|
||||
// @IBOutlet weak var videoPlayerHeight: NSLayoutConstraint!
|
||||
@IBOutlet weak var viewControll: UIView!
|
||||
@IBOutlet weak var stackCtrView: UIStackView!
|
||||
|
||||
@@ -47,7 +47,7 @@ class AVPlayerVC: UIViewController {
|
||||
self.seekSlider.addTarget(self, action: #selector(onTapToSlide), for: .valueChanged)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
var videoURL : String?
|
||||
var timer : Timer?
|
||||
@@ -60,6 +60,7 @@ class AVPlayerVC: UIViewController {
|
||||
vm.initView()
|
||||
self.videoTitle.text = titleVideo
|
||||
startTimer()
|
||||
self.setObserverToPlayer()
|
||||
|
||||
viewControll.addTapGesture {
|
||||
self.timer?.invalidate()
|
||||
@@ -87,6 +88,11 @@ class AVPlayerVC: UIViewController {
|
||||
self.player = nil
|
||||
}
|
||||
|
||||
deinit {
|
||||
NotificationCenter.default.removeObserver(self)
|
||||
player?.currentItem?.removeObserver(self, forKeyPath: "status")
|
||||
}
|
||||
|
||||
// MARK: - ShowHideControls
|
||||
|
||||
func showHideControls(){
|
||||
@@ -98,8 +104,8 @@ class AVPlayerVC: UIViewController {
|
||||
startTimer()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private var player : AVPlayer? = nil
|
||||
private var playerLayer : AVPlayerLayer? = nil
|
||||
|
||||
@@ -107,40 +113,45 @@ class AVPlayerVC: UIViewController {
|
||||
guard let videoURL, let url = URL(string: videoURL) else { return }
|
||||
|
||||
if self.player == nil {
|
||||
self.player = AVPlayer(url: url)
|
||||
self.playerLayer = AVPlayerLayer(player: self.player)
|
||||
self.playerLayer?.videoGravity = .resizeAspectFill
|
||||
self.playerLayer?.frame = self.videoPlayer.bounds
|
||||
self.playerLayer?.addSublayer(self.viewControll.layer)
|
||||
if let playerLayer = self.playerLayer {
|
||||
self.videoPlayer.layer.addSublayer(playerLayer)
|
||||
// Initialize the player
|
||||
let playerItem = AVPlayerItem(url: url)
|
||||
player = AVPlayer(playerItem: playerItem)
|
||||
|
||||
// Add observer for AVPlayerItemFailedToPlayToEndTimeNotification
|
||||
NotificationCenter.default.addObserver(self,
|
||||
selector: #selector(playerItemFailedToPlayToEndTime(_:)),
|
||||
name: .AVPlayerItemFailedToPlayToEndTime,
|
||||
object: playerItem)
|
||||
|
||||
// Add observer for AVPlayerItem's status
|
||||
playerItem.addObserver(self,
|
||||
forKeyPath: "status",
|
||||
options: [.new, .initial],
|
||||
context: nil)
|
||||
|
||||
// Set up the player layer
|
||||
playerLayer = AVPlayerLayer(player: player)
|
||||
playerLayer?.videoGravity = .resizeAspectFill
|
||||
playerLayer?.frame = videoPlayer.bounds
|
||||
|
||||
if let playerLayer = playerLayer {
|
||||
videoPlayer.layer.addSublayer(playerLayer)
|
||||
}
|
||||
self.player?.play()
|
||||
self.imgPlay.image = UIImage(systemName: "pause.circle")
|
||||
|
||||
|
||||
// self.player = AVPlayer(url: url)
|
||||
// self.playerLayer = AVPlayerLayer(player: self.player)
|
||||
// self.playerLayer?.videoGravity = .resizeAspectFill
|
||||
// self.playerLayer?.frame = self.videoPlayer.bounds
|
||||
// self.playerLayer?.addSublayer(self.viewControll.layer)
|
||||
// if let playerLayer = self.playerLayer {
|
||||
// self.videoPlayer.layer.addSublayer(playerLayer)
|
||||
// }
|
||||
// self.player?.play()
|
||||
// self.imgPlay.image = UIImage(systemName: "pause.circle")
|
||||
}
|
||||
self.setObserverToPlayer()
|
||||
}
|
||||
|
||||
|
||||
// private var windowInterface : UIInterfaceOrientation? {
|
||||
// return self.view.window?.windowScene?.interfaceOrientation
|
||||
// }
|
||||
|
||||
// override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) {
|
||||
// super.willTransition(to: newCollection, with: coordinator)
|
||||
// guard let windowInterface = self.windowInterface else { return }
|
||||
// if windowInterface.isPortrait == true {
|
||||
// self.videoPlayerHeight.constant = 300
|
||||
// } else {
|
||||
// self.videoPlayerHeight.constant = self.view.layer.bounds.width
|
||||
// }
|
||||
// print(self.videoPlayerHeight.constant)
|
||||
// DispatchQueue.main.asyncAfter(deadline: .now() + 0.1, execute: {
|
||||
// self.playerLayer?.frame = self.videoPlayer.bounds
|
||||
// })
|
||||
// }
|
||||
|
||||
|
||||
private var timeObserver : Any? = nil
|
||||
|
||||
private func setObserverToPlayer() {
|
||||
@@ -150,6 +161,55 @@ class AVPlayerVC: UIViewController {
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// Handle notification
|
||||
@objc func playerItemFailedToPlayToEndTime(_ notification: Notification) {
|
||||
if let error = notification.userInfo?[AVPlayerItemFailedToPlayToEndTimeErrorKey] as? Error {
|
||||
print("Error: \(error.localizedDescription)")
|
||||
handlePlayerError(error)
|
||||
}
|
||||
}
|
||||
|
||||
// Observe for player item status changes
|
||||
override func observeValue(forKeyPath keyPath: String?,
|
||||
of object: Any?,
|
||||
change: [NSKeyValueChangeKey : Any]?,
|
||||
context: UnsafeMutableRawPointer?) {
|
||||
if keyPath == "status" {
|
||||
if let playerItem = object as? AVPlayerItem {
|
||||
switch playerItem.status {
|
||||
case .readyToPlay:
|
||||
// Ready to play
|
||||
print("Player item is ready to play.")
|
||||
player?.play()
|
||||
imgPlay.image = UIImage(systemName: "pause.circle")
|
||||
case .failed:
|
||||
// Failed
|
||||
if let error = playerItem.error {
|
||||
print("Player item error: \(error.localizedDescription)")
|
||||
handlePlayerError(error)
|
||||
}
|
||||
case .unknown:
|
||||
// Unknown status
|
||||
print("Player item status is unknown.")
|
||||
@unknown default:
|
||||
// Handle unexpected cases
|
||||
print("Player item status is unknown.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handlePlayerError(_ error: Error) {
|
||||
// Update the UI or show an alert to the user
|
||||
Utilities.alertWithBtnCompletion(title: "Error", msgBody: error.localizedDescription, okBtnStr: "Retry?", vc: self) { [weak self] isDone in
|
||||
guard let self else{return}
|
||||
player?.play()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private func updatePlayerTime() {
|
||||
guard let currentTime = self.player?.currentTime() else { return }
|
||||
guard let duration = self.player?.currentItem?.duration else { return }
|
||||
@@ -232,7 +292,7 @@ class AVPlayerVC: UIViewController {
|
||||
if completed {
|
||||
self.isThumbSeek = false
|
||||
self.startTimer()
|
||||
// print("Completed")
|
||||
// print("Completed")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
352
WOKA/Karaoke/Controller/JWKaraokePlayerVC.swift
Normal file
352
WOKA/Karaoke/Controller/JWKaraokePlayerVC.swift
Normal file
@@ -0,0 +1,352 @@
|
||||
//
|
||||
// JWKaraokePlayerVC.swift
|
||||
// WOKA
|
||||
//
|
||||
// Created by MacBook Pro on 09/07/24.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import JWPlayerKit
|
||||
import AVFAudio
|
||||
import AVFoundation
|
||||
|
||||
class JWKaraokePlayerVC: JWPlayerViewController, JWPlayerViewControllerDelegate {
|
||||
|
||||
@IBOutlet weak var outerStack: UIStackView!
|
||||
@IBOutlet weak var backButton: UIButton!
|
||||
@IBOutlet weak var startRecordBtn: LocalisedElementsButton!
|
||||
@IBOutlet weak var playBtn: LocalisedElementsButton!
|
||||
@IBOutlet weak var downloadRecordingBtn: LocalisedElementsButton!
|
||||
|
||||
var config: JWPlayerConfiguration!
|
||||
var dismissTapped: (() -> Void)?
|
||||
var videoIndex : Int?
|
||||
var documentAudioUrl : URL?
|
||||
var recordedAudioURL: URL?
|
||||
var audioRecorder: AVAudioRecorder?
|
||||
var isRecording = false
|
||||
var playerAV : AVAudioPlayer?
|
||||
|
||||
var mixedAudioURL: URL? {
|
||||
didSet{
|
||||
do{
|
||||
let sPlayer = try AVAudioPlayer(contentsOf: self.mixedAudioURL!)
|
||||
self.playerAV = sPlayer
|
||||
self.playerAV?.prepareToPlay()
|
||||
// self.playerAV?.play()
|
||||
}catch{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
player.configurePlayer(with: config)
|
||||
|
||||
self.delegate = self
|
||||
|
||||
//Disable Picture in Picture
|
||||
playerView.allowsPictureInPicturePlayback = false
|
||||
playerView.captionStyle = .none
|
||||
|
||||
self.view.bringSubviewToFront(outerStack)
|
||||
self.view.bringSubviewToFront(backButton)
|
||||
setupRecorder()
|
||||
}
|
||||
|
||||
func setupRecorder(){
|
||||
let audioSession = AVAudioSession.sharedInstance()
|
||||
do {
|
||||
try audioSession.setCategory(.playAndRecord, mode: .default,options: .defaultToSpeaker)
|
||||
try audioSession.setActive(true)
|
||||
|
||||
|
||||
// Define settings for the audio recorder
|
||||
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
|
||||
let outputURL = documentsDirectory.appendingPathComponent("recordedAudio.m4a")
|
||||
let settings: [String: Any] = [
|
||||
AVFormatIDKey: kAudioFormatMPEG4AAC,
|
||||
AVSampleRateKey: 44100,
|
||||
AVNumberOfChannelsKey: 2,
|
||||
AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue
|
||||
]
|
||||
|
||||
// Initialize AVAudioRecorder with the output URL and settings
|
||||
audioRecorder = try AVAudioRecorder(url: outputURL, settings: settings)
|
||||
audioRecorder?.prepareToRecord()
|
||||
recordedAudioURL = outputURL // Store the recorded audio URL
|
||||
} catch {
|
||||
print("Error setting up audio: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
func startRecording() {
|
||||
guard let player = player, let audioRecorder = audioRecorder else { return }
|
||||
player.play()
|
||||
audioRecorder.record()
|
||||
}
|
||||
|
||||
func stopRecording() {
|
||||
guard let player = player, let audioRecorder = audioRecorder else { return }
|
||||
player.pause() // Pause playback instead of stopping it
|
||||
audioRecorder.stop()
|
||||
|
||||
// Mix the recorded audio with the downloaded M4A file
|
||||
mixAudio()
|
||||
}
|
||||
|
||||
func mixAudio() {
|
||||
guard let recordedAudioURL = recordedAudioURL else { return }
|
||||
// guard let playerURL = Bundle.main.url(forResource: "Sample_audio", withExtension: "m4a") else { return }
|
||||
guard let playerURL = documentAudioUrl else{return}
|
||||
let composition = AVMutableComposition()
|
||||
|
||||
// Add the recorded audio
|
||||
let recordedAudioAsset = AVURLAsset(url: recordedAudioURL)
|
||||
let recordedAudioTrack = composition.addMutableTrack(withMediaType: .audio, preferredTrackID: kCMPersistentTrackID_Invalid)
|
||||
do {
|
||||
try recordedAudioTrack?.insertTimeRange(CMTimeRangeMake(start: CMTime.zero, duration: recordedAudioAsset.duration), of: recordedAudioAsset.tracks(withMediaType: .audio)[0], at: CMTime.zero)
|
||||
} catch {
|
||||
print("Error adding recorded audio track: \(error.localizedDescription)")
|
||||
}
|
||||
|
||||
// Add the downloaded M4A file
|
||||
let playerAsset = AVURLAsset(url: playerURL)
|
||||
let playerTrack = composition.addMutableTrack(withMediaType: .audio, preferredTrackID: kCMPersistentTrackID_Invalid)
|
||||
|
||||
do {
|
||||
try playerTrack?.insertTimeRange(CMTimeRangeMake(start: CMTime.zero, duration: playerAsset.duration), of: playerAsset.tracks(withMediaType: .audio)[0], at: CMTime.zero)
|
||||
} catch {
|
||||
print("Error adding player audio track: \(error.localizedDescription)")
|
||||
}
|
||||
|
||||
// Export the mixed audio
|
||||
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
|
||||
mixedAudioURL = documentsDirectory.appendingPathComponent("mixedAudio.m4a")
|
||||
guard let exportSession = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetAppleM4A) else { return }
|
||||
exportSession.outputURL = mixedAudioURL
|
||||
exportSession.outputFileType = .m4a
|
||||
exportSession.exportAsynchronously {
|
||||
if exportSession.status == .completed {
|
||||
print("Mixing audio completed.")
|
||||
|
||||
self.saveToFilesApp()
|
||||
// Play the mixed audio if needed
|
||||
} else if exportSession.status == .failed {
|
||||
print("Mixing audio failed.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func saveToFilesApp() {
|
||||
guard let mixedAudioURL = mixedAudioURL else { return }
|
||||
DispatchQueue.main.async {
|
||||
let documentPicker = UIDocumentPickerViewController(url: mixedAudioURL, in: .exportToService)
|
||||
documentPicker.delegate = self
|
||||
self.present(documentPicker, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
// self.navigationController?.isNavigationBarHidden = true
|
||||
}
|
||||
|
||||
override func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
player.stop()
|
||||
}
|
||||
|
||||
@IBAction func startRecordingBtnTapped(_ sender: LocalisedElementsButton) {
|
||||
// if sender.titleLabel?.text?.lowercased() == "start recording"{
|
||||
// guard let audioRecorder else { return }
|
||||
// startRecordBtn.setTitle("Stop Recording", for: .normal)
|
||||
// audioRecorder.record()
|
||||
// }else{
|
||||
// guard let audioRecorder else { return }
|
||||
// startRecordBtn.setTitle("Stop Recording", for: .normal)
|
||||
// audioRecorder.record()
|
||||
// }
|
||||
|
||||
if isRecording {
|
||||
stopRecording()
|
||||
sender.setTitle("Start Recording", for: .normal)
|
||||
} else {
|
||||
startRecording()
|
||||
sender.setTitle("Stop Recording", for: .normal)
|
||||
}
|
||||
isRecording.toggle()
|
||||
}
|
||||
|
||||
@IBAction func playBtnTapped(_ sender: LocalisedElementsButton) {
|
||||
playerAV?.play()
|
||||
}
|
||||
|
||||
|
||||
@IBAction func downloadRecording(_ sender: LocalisedElementsButton) {
|
||||
print("DownloadRecording")
|
||||
}
|
||||
@IBAction func backBtnTapped(_ sender: UIButton) {
|
||||
self.player.stop()
|
||||
self.dismiss(animated: true)
|
||||
}
|
||||
|
||||
// MARK: - JWPlayerViewControllerDelegate
|
||||
|
||||
override func jwplayer(_ player: any JWPlayer, didFinishLoadingWithTime loadTime: TimeInterval) {
|
||||
super.jwplayer(player, didFinishLoadingWithTime: loadTime)
|
||||
print("LoadTime", loadTime)
|
||||
DispatchQueue.main.async {
|
||||
self.startRecordBtn.isEnabled = true
|
||||
}
|
||||
}
|
||||
|
||||
//Playlist Functions
|
||||
|
||||
// override func jwplayerHasSeeked(_ player: any JWPlayer) {
|
||||
//// if player.getState() != .playing{
|
||||
//// print("Again Play")
|
||||
//// player.play()
|
||||
//// }
|
||||
// print("Seeked " , player.getState())
|
||||
// }
|
||||
|
||||
override func jwplayerIsReady(_ player: JWPlayer) {
|
||||
super.jwplayerIsReady(player)
|
||||
player.seek(to: 0)
|
||||
|
||||
print("IsReady")
|
||||
}
|
||||
|
||||
override func jwplayer(_ player: JWPlayer, failedWithSetupError code: UInt, message: String) {
|
||||
super.jwplayer(player, failedWithSetupError: code, message: message)
|
||||
print("Setup Error: \(code) - \(message)")
|
||||
}
|
||||
|
||||
override func jwplayer(_ player: JWPlayer, failedWithError code: UInt, message: String) {
|
||||
super.jwplayer(player, failedWithError: code, message: message)
|
||||
// if no internet then add network observer
|
||||
print("Error: \(code) - \(message)")
|
||||
}
|
||||
|
||||
override func jwplayer(_ player: JWPlayer, encounteredWarning code: UInt, message: String) {
|
||||
super.jwplayer(player, encounteredWarning: code, message: message)
|
||||
print("Warning: \(code) - \(message)")
|
||||
}
|
||||
|
||||
override func jwplayer(_ player: JWPlayer, encounteredAdError code: UInt, message: String) {
|
||||
super.jwplayer(player, encounteredAdError: code, message: message)
|
||||
print("Ad Error: \(code) - \(message)")
|
||||
}
|
||||
|
||||
override func jwplayer(_ player: JWPlayer, encounteredAdWarning code: UInt, message: String) {
|
||||
super.jwplayer(player, encounteredAdWarning: code, message: message)
|
||||
print("Ad Warning: \(code) - \(message)")
|
||||
}
|
||||
|
||||
override func jwplayer(_ player: JWPlayer, isBufferingWithReason reason: JWBufferReason) {
|
||||
super.jwplayer(player, isBufferingWithReason: reason)
|
||||
// player.play()
|
||||
print("Buffering Reason:", reason)
|
||||
}
|
||||
|
||||
override func jwplayer(_ player: JWPlayer, didPauseWithReason reason: JWPauseReason) {
|
||||
super.jwplayer(player, didPauseWithReason: reason)
|
||||
// Implement custom behavior
|
||||
}
|
||||
}
|
||||
|
||||
extension JWKaraokePlayerVC: UIDocumentPickerDelegate {
|
||||
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
|
||||
// Handle document picking completion if needed
|
||||
}
|
||||
}
|
||||
// MARK: - Full Screen Handling
|
||||
|
||||
extension JWKaraokePlayerVC {
|
||||
|
||||
func playerViewControllerWillGoFullScreen(_ controller: JWPlayerViewController) -> JWFullScreenViewController? {
|
||||
// controller.shouldEnterFullScreen = false
|
||||
// self.setDeviceOrientation(orientation: .portrait)
|
||||
self.player.stop()
|
||||
self.dismiss(animated: true)
|
||||
print("playerViewControllerWillGoFullScreen")
|
||||
return nil
|
||||
}
|
||||
|
||||
func playerViewControllerDidGoFullScreen(_ controller: JWPlayerViewController) {
|
||||
print("playerViewControllerDidGoFullScreen")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func playerViewControllerWillDismissFullScreen(_ controller: JWPlayerViewController) {
|
||||
print("playerViewControllerWillDismissFullScreen")
|
||||
|
||||
}
|
||||
|
||||
func playerViewControllerDidDismissFullScreen(_ controller: JWPlayerViewController) {
|
||||
print("playerViewControllerDidDismissFullScreen")
|
||||
|
||||
self.dismissTapped?()
|
||||
Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false) { _ in
|
||||
self.player.stop()
|
||||
controller.dismiss(animated: true)
|
||||
}
|
||||
// self.navigationController?.popViewController(animated: true)
|
||||
}
|
||||
// func playerViewControllerWillGoFullScreen(_ controller: JWPlayerViewController) -> JWFullScreenViewController? {
|
||||
// print("playerViewControllerWillGoFullScreen")
|
||||
// return nil
|
||||
// }
|
||||
//
|
||||
// func playerViewControllerDidGoFullScreen(_ controller: JWPlayerViewController) {
|
||||
// print("playerViewControllerDidGoFullScreen")
|
||||
// }
|
||||
//
|
||||
// func playerViewControllerWillDismissFullScreen(_ controller: JWPlayerViewController) {
|
||||
// print("playerViewControllerWillDismissFullScreen")
|
||||
// self.player.stop()
|
||||
// self.dismissTapped?()
|
||||
// self.setDeviceOrientation(orientation: .portrait)
|
||||
// }
|
||||
//
|
||||
// func playerViewControllerDidDismissFullScreen(_ controller: JWPlayerViewController) {
|
||||
// print("playerViewControllerDidDismissFullScreen")
|
||||
// Timer.scheduledTimer(withTimeInterval: 0.3, repeats: false) { _ in
|
||||
// self.navigationController?.popViewController(animated: true)
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
// MARK: - JWPlayerViewController Delegate Functions
|
||||
|
||||
extension JWKaraokePlayerVC {
|
||||
|
||||
func playerViewController(_ controller: JWPlayerKit.JWPlayerViewController, controlBarVisibilityChanged isVisible: Bool, frame: CGRect) {
|
||||
self.backButton.isHidden = !isVisible
|
||||
// backButton.isHidden = !isVisible
|
||||
}
|
||||
|
||||
func playerViewController(_ controller: JWPlayerKit.JWPlayerViewController, sizeChangedFrom oldSize: CGSize, to newSize: CGSize) {
|
||||
// Handle size change if necessary
|
||||
}
|
||||
|
||||
func playerViewController(_ controller: JWPlayerKit.JWPlayerViewController, screenTappedAt position: CGPoint) {
|
||||
// Handle screen tap if necessary
|
||||
}
|
||||
|
||||
func playerViewController(_ controller: JWPlayerKit.JWPlayerViewController, relatedMenuOpenedWithItems items: [JWPlayerKit.JWPlayerItem], withMethod method: JWPlayerKit.JWRelatedInteraction) {
|
||||
print("Related items:", items)
|
||||
}
|
||||
|
||||
func playerViewController(_ controller: JWPlayerKit.JWPlayerViewController, relatedMenuClosedWithMethod method: JWRelatedInteraction) {
|
||||
print("Related menu closed")
|
||||
}
|
||||
|
||||
func playerViewController(_ controller: JWPlayerKit.JWPlayerViewController, relatedItemBeganPlaying item: JWPlayerKit.JWPlayerItem, atIndex index: Int, withMethod method: JWPlayerKit.JWRelatedMethod) {
|
||||
print("Item ", item)
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,8 @@
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import JWPlayerKit
|
||||
import AVFoundation
|
||||
|
||||
class KaraokeDetailsVC: UIViewController {
|
||||
|
||||
@@ -157,25 +159,74 @@ class KaraokeDetailsVC: UIViewController {
|
||||
}
|
||||
|
||||
@IBAction func playNowBtnTapped(_ sender: LocalisedElementsButton) {
|
||||
let sb = UIStoryboard(name: K.StoryBoard.Karaoke, bundle: nil)
|
||||
let vcPush = sb.instantiateViewController(withIdentifier: K.StoryBoardID.Karaoke.aVPlayerVC) as! AVPlayerVC
|
||||
|
||||
// let sb = UIStoryboard(name: K.StoryBoard.Karaoke, bundle: nil)
|
||||
// let vcPush = sb.instantiateViewController(withIdentifier: "TestingKaraokeVC") as! TestingKaraokeVC
|
||||
// self.present(vcPush, animated: true)
|
||||
// return
|
||||
var itemBuild = JwPlayerItemCreate(url: "")
|
||||
|
||||
if AuthFunc.shareInstance.getDefaultLanguage() == .english{
|
||||
if let englishData = karaokeData?.contentMoreDetails?.filter({$0.languageMasterID == 1}).first{
|
||||
vcPush.titleVideo = englishData.title
|
||||
vcPush.videoURL = englishData.url
|
||||
guard let url = englishData.url , let title = englishData.title else{return}
|
||||
itemBuild = JwPlayerItemCreate(url: url, poster: karaokeData?.thumbnailPath, titles: title)
|
||||
}
|
||||
}else{
|
||||
if let hindiData = karaokeData?.contentMoreDetails?.filter({$0.languageMasterID == 2}).first{
|
||||
vcPush.titleVideo = hindiData.title
|
||||
vcPush.videoURL = hindiData.url
|
||||
guard let url = hindiData.url , let title = hindiData.title else{return}
|
||||
itemBuild = JwPlayerItemCreate(url: url, poster: karaokeData?.thumbnailPath, titles: title)
|
||||
}
|
||||
}
|
||||
|
||||
let avURL = URL(string: itemBuild.url)!
|
||||
let asset = AVAsset(url: avURL)
|
||||
|
||||
let outputURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("\(karaokeData?.title?.trimmingCharacters(in: .whitespaces) ?? "extractedAudio").m4a")
|
||||
FileManager.default.clearTmpDirectory()
|
||||
asset.writeAudioTrackToURL(outputURL) { isDone, error,url in
|
||||
print(isDone, error , url)
|
||||
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self else{return}
|
||||
Utilities.startProgressHUD()
|
||||
let sb = UIStoryboard(name: K.StoryBoard.Karaoke, bundle: nil)
|
||||
let vc = sb.instantiateViewController(withIdentifier: K.StoryBoardID.Karaoke.jwKaraokePlayerVC) as! JWKaraokePlayerVC
|
||||
do {
|
||||
|
||||
// Create a JWPlayerItem
|
||||
let item = try JWPlayerItemBuilder()
|
||||
.file(URL(string: itemBuild.url)!)
|
||||
.posterImage(URL(string: itemBuild.poster ?? "")!)
|
||||
.title(itemBuild.titles ?? "Karaoke")
|
||||
.build()
|
||||
|
||||
// Create a JWPlayerConfiguration
|
||||
let config = try JWPlayerConfigurationBuilder()
|
||||
.playlist(items: [item])
|
||||
.autostart(true)
|
||||
.build()
|
||||
|
||||
vc.config = config
|
||||
// vc.dismissTapped = self.tapped
|
||||
vc.modalPresentationStyle = .overFullScreen
|
||||
vc.modalTransitionStyle = .crossDissolve
|
||||
vc.documentAudioUrl = url
|
||||
Utilities.dismissProgressHUD()
|
||||
// Present the PlayerVC
|
||||
self.present(vc, animated: true)
|
||||
} catch {
|
||||
print("Error creating JWPlayer configuration: \(error)")
|
||||
Utilities.dismissProgressHUD()
|
||||
}
|
||||
|
||||
// Dismiss the progress HUD after the view controller presentation
|
||||
Utilities.dismissProgressHUD()
|
||||
|
||||
}
|
||||
}
|
||||
self.present(vcPush, animated: true)
|
||||
}
|
||||
|
||||
@IBAction func closeBtnTapped(_ sender: UIButton) {
|
||||
self.dismiss(animated: true) {
|
||||
|
||||
}
|
||||
self.dismiss(animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
120
WOKA/Karaoke/Controller/TestingKaraokeVC.swift
Normal file
120
WOKA/Karaoke/Controller/TestingKaraokeVC.swift
Normal file
@@ -0,0 +1,120 @@
|
||||
//
|
||||
// TestingKaraokeVC.swift
|
||||
// WOKA
|
||||
//
|
||||
// Created by MacBook Pro on 09/07/24.
|
||||
//
|
||||
|
||||
import AVFAudio
|
||||
import UIKit
|
||||
import AVFoundation
|
||||
|
||||
class TestingKaraokeVC : UIViewController{
|
||||
|
||||
var player: AVPlayer!
|
||||
var audioEngine: AVAudioEngine!
|
||||
var micInput: AVAudioInputNode!
|
||||
var playerNode: AVAudioPlayerNode!
|
||||
var audioFile: AVAudioFile!
|
||||
var fileURL: URL!
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
let videoURL = URL(string: "https://content.jwplatform.com/videos/DOhtETio-Ysj2G4DQ.mp4")!
|
||||
setupPlayer(with: videoURL)
|
||||
setupAudioEngine()
|
||||
setupAudioFile()
|
||||
}
|
||||
|
||||
func setupPlayer(with url: URL) {
|
||||
player = AVPlayer(url: url)
|
||||
let playerLayer = AVPlayerLayer(player: player)
|
||||
playerLayer.frame = view.bounds
|
||||
view.layer.addSublayer(playerLayer)
|
||||
player.play()
|
||||
}
|
||||
|
||||
func setupAudioEngine() {
|
||||
audioEngine = AVAudioEngine()
|
||||
micInput = audioEngine.inputNode
|
||||
playerNode = AVAudioPlayerNode()
|
||||
|
||||
audioEngine.attach(playerNode)
|
||||
let format = micInput.inputFormat(forBus: 0)
|
||||
|
||||
audioEngine.connect(micInput, to: audioEngine.mainMixerNode, format: format)
|
||||
audioEngine.connect(playerNode, to: audioEngine.mainMixerNode, format: format)
|
||||
|
||||
try! audioEngine.start()
|
||||
}
|
||||
|
||||
func setupAudioFile() {
|
||||
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
|
||||
fileURL = documentsDirectory.appendingPathComponent("output.m4a")
|
||||
let format = audioEngine.mainMixerNode.outputFormat(forBus: 0)
|
||||
|
||||
do {
|
||||
audioFile = try AVAudioFile(forWriting: fileURL, settings: format.settings)
|
||||
} catch {
|
||||
print("Error creating audio file: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func startRecording() {
|
||||
let mixer = audioEngine.mainMixerNode
|
||||
let format = mixer.outputFormat(forBus: 0)
|
||||
|
||||
mixer.installTap(onBus: 0, bufferSize: 1024, format: format) { (buffer, time) in
|
||||
do {
|
||||
try self.audioFile.write(from: buffer)
|
||||
} catch {
|
||||
print("Error writing audio buffer: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func stopRecording() {
|
||||
let mixer = audioEngine.mainMixerNode
|
||||
mixer.removeTap(onBus: 0)
|
||||
|
||||
// Save the file to Files app
|
||||
presentDocumentPicker()
|
||||
}
|
||||
|
||||
func presentDocumentPicker() {
|
||||
// let documentPicker = UIDocumentPickerViewController(forExporting: [fileURL])
|
||||
// documentPicker.delegate = self
|
||||
// present(documentPicker, animated: true, completion: nil)
|
||||
|
||||
guard let mixedAudioURL = fileURL else { return }
|
||||
DispatchQueue.main.async {
|
||||
let documentPicker = UIDocumentPickerViewController(url: mixedAudioURL, in: .exportToService)
|
||||
documentPicker.delegate = self
|
||||
self.present(documentPicker, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func startButtonPressed(_ sender: UIButton) {
|
||||
startRecording()
|
||||
}
|
||||
|
||||
@IBAction func stopButtonPressed(_ sender: UIButton) {
|
||||
stopRecording()
|
||||
}
|
||||
}
|
||||
|
||||
extension TestingKaraokeVC: UIDocumentPickerDelegate {
|
||||
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
|
||||
guard let selectedURL = urls.first else { return }
|
||||
do {
|
||||
try FileManager.default.moveItem(at: fileURL, to: selectedURL)
|
||||
} catch {
|
||||
print("Error saving file to Files app: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
|
||||
print("Document picker was cancelled")
|
||||
}
|
||||
}
|
||||
@@ -581,17 +581,17 @@
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" translatesAutoresizingMaskIntoConstraints="NO" id="s55-L0-QBA" userLabel="PlayerView">
|
||||
<rect key="frame" x="0.0" y="330" width="414" height="250"/>
|
||||
<rect key="frame" x="0.0" y="330" width="418" height="250"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" translatesAutoresizingMaskIntoConstraints="NO" id="l7y-7N-uz2">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="250"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="418" height="250"/>
|
||||
<subviews>
|
||||
<view alpha="0.5" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="7Lc-Yp-x8G">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="250"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="418" height="250"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</view>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" spacing="6" translatesAutoresizingMaskIntoConstraints="NO" id="Mhh-bR-zKm" userLabel="SliderStack">
|
||||
<rect key="frame" x="5" y="218" width="404" height="30"/>
|
||||
<rect key="frame" x="5" y="218" width="408" height="30"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="00:00:00" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="PZq-WO-H32">
|
||||
<rect key="frame" x="0.0" y="0.0" width="50" height="30"/>
|
||||
@@ -603,12 +603,12 @@
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<slider opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="VKq-Xn-4Nn">
|
||||
<rect key="frame" x="54" y="0.0" width="296" height="31"/>
|
||||
<rect key="frame" x="54" y="0.0" width="300" height="31"/>
|
||||
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<color key="thumbTintColor" red="0.80000000000000004" green="0.29411764709999999" blue="0.1137254902" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</slider>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" usesAttributedText="YES" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="0xZ-En-mzk">
|
||||
<rect key="frame" x="354" y="0.0" width="50" height="30"/>
|
||||
<rect key="frame" x="358" y="0.0" width="50" height="30"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="50" id="13R-ht-Ooc"/>
|
||||
</constraints>
|
||||
@@ -626,7 +626,7 @@
|
||||
</subviews>
|
||||
</stackView>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="6xv-b4-3da">
|
||||
<rect key="frame" x="112" y="100" width="190" height="50"/>
|
||||
<rect key="frame" x="114" y="100" width="190" height="50"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="M0h-b5-LXf">
|
||||
<rect key="frame" x="0.0" y="0.0" width="50" height="50"/>
|
||||
@@ -707,16 +707,16 @@
|
||||
</constraints>
|
||||
</view>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" spacing="10" translatesAutoresizingMaskIntoConstraints="NO" id="hXe-Fv-Zk9">
|
||||
<rect key="frame" x="10" y="58" width="394" height="30"/>
|
||||
<rect key="frame" x="10" y="58" width="398" height="30"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="NA" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="alm-nS-A2I">
|
||||
<rect key="frame" x="0.0" y="0.0" width="354" height="30"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="358" height="30"/>
|
||||
<fontDescription key="fontDescription" name="Exo2-Bold" family="Exo 2" pointSize="18"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="jlb-0g-59l">
|
||||
<rect key="frame" x="364" y="0.0" width="30" height="30"/>
|
||||
<rect key="frame" x="368" y="0.0" width="30" height="30"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" secondItem="jlb-0g-59l" secondAttribute="height" multiplier="1:1" id="9KW-Ku-fD3"/>
|
||||
<constraint firstAttribute="width" secondItem="jlb-0g-59l" secondAttribute="height" multiplier="1:1" id="Ig1-El-cep"/>
|
||||
@@ -732,13 +732,13 @@
|
||||
</subviews>
|
||||
</stackView>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="15" translatesAutoresizingMaskIntoConstraints="NO" id="jTd-8Q-0y7">
|
||||
<rect key="frame" x="25" y="658" width="364" height="96"/>
|
||||
<rect key="frame" x="25" y="658" width="368" height="96"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" distribution="fillEqually" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="gLz-mh-ln0">
|
||||
<rect key="frame" x="0.0" y="0.0" width="364" height="50"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="368" height="50"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="tailTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="4KA-5y-qg7" customClass="LocalisedElementsButton" customModule="WOKA" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="172" height="50"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="174" height="50"/>
|
||||
<color key="backgroundColor" red="0.80000000000000004" green="0.29411764709999999" blue="0.1137254902" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="50" id="3ZS-GJ-VKY"/>
|
||||
@@ -758,7 +758,7 @@
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="249" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="tailTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="dz2-DJ-auS" customClass="LocalisedElementsButton" customModule="WOKA" customModuleProvider="target">
|
||||
<rect key="frame" x="192" y="0.0" width="172" height="50"/>
|
||||
<rect key="frame" x="194" y="0.0" width="174" height="50"/>
|
||||
<color key="backgroundColor" name="TextDarkBlue"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="50" id="7d9-YY-TdQ"/>
|
||||
@@ -781,7 +781,7 @@
|
||||
</subviews>
|
||||
</stackView>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="tailTruncation" hasAttributedTitle="YES" translatesAutoresizingMaskIntoConstraints="NO" id="k45-7s-jnN" customClass="LocalisedElementsButton" customModule="WOKA" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="65" width="364" height="31"/>
|
||||
<rect key="frame" x="0.0" y="65" width="368" height="31"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<inset key="titleEdgeInsets" minX="5" minY="0.0" maxX="0.0" maxY="0.0"/>
|
||||
@@ -854,8 +854,193 @@
|
||||
</objects>
|
||||
<point key="canvasLocation" x="1657.9710144927537" y="-27.455357142857142"/>
|
||||
</scene>
|
||||
<!--Karaoke PlayerVC-->
|
||||
<scene sceneID="76g-qy-5ia">
|
||||
<objects>
|
||||
<viewController storyboardIdentifier="JWKaraokePlayerVC" id="9gy-Qq-XHU" customClass="JWKaraokePlayerVC" customModule="WOKA" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="h1e-2a-Kjb">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="15" translatesAutoresizingMaskIntoConstraints="NO" id="lIa-J9-DOG">
|
||||
<rect key="frame" x="20" y="606.5" width="374" height="97"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" distribution="fillEqually" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="hIr-xj-FN5">
|
||||
<rect key="frame" x="0.0" y="0.0" width="374" height="50"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" enabled="NO" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="tailTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="yv1-I1-YGZ" customClass="LocalisedElementsButton" customModule="WOKA" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="177" height="50"/>
|
||||
<color key="backgroundColor" red="1" green="0.044904738164268321" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="displayP3"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="50" id="N39-39-T3E"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" name="Exo2-Bold" family="Exo 2" pointSize="14"/>
|
||||
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<inset key="titleEdgeInsets" minX="5" minY="0.0" maxX="0.0" maxY="0.0"/>
|
||||
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="5" maxY="0.0"/>
|
||||
<state key="normal" title="Start Recording" image="Microphone"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
|
||||
<integer key="value" value="25"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
</userDefinedRuntimeAttributes>
|
||||
<connections>
|
||||
<action selector="playNowBtnTapped:" destination="fax-bi-Mb9" eventType="touchUpInside" id="Ruq-Xc-nQi"/>
|
||||
<action selector="startRecordingBtnTapped:" destination="9gy-Qq-XHU" eventType="touchUpInside" id="SOw-KK-zIp"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="249" enabled="NO" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="tailTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="mHM-7B-XXx" customClass="LocalisedElementsButton" customModule="WOKA" customModuleProvider="target">
|
||||
<rect key="frame" x="197" y="0.0" width="177" height="50"/>
|
||||
<color key="backgroundColor" name="TextDarkBlue"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="50" id="nIb-2q-pnZ"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" name="Exo2-Bold" family="Exo 2" pointSize="16"/>
|
||||
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<inset key="titleEdgeInsets" minX="5" minY="0.0" maxX="0.0" maxY="0.0"/>
|
||||
<inset key="imageEdgeInsets" minX="-10" minY="0.0" maxX="10" maxY="0.0"/>
|
||||
<state key="normal" title="Play" image="PlayButtonSmall"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
|
||||
<integer key="value" value="25"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
</userDefinedRuntimeAttributes>
|
||||
<connections>
|
||||
<action selector="playBtnTapped:" destination="9gy-Qq-XHU" eventType="touchUpInside" id="tLm-vf-dht"/>
|
||||
<action selector="playNowBtnTapped:" destination="fax-bi-Mb9" eventType="touchUpInside" id="VzV-ZD-BbA"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
</stackView>
|
||||
<button opaque="NO" contentMode="scaleToFill" enabled="NO" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="tailTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="3hw-ks-w1o" customClass="LocalisedElementsButton" customModule="WOKA" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="65" width="374" height="32"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<fontDescription key="fontDescription" name="Exo2-Regular" family="Exo 2" pointSize="16"/>
|
||||
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<inset key="titleEdgeInsets" minX="5" minY="0.0" maxX="0.0" maxY="0.0"/>
|
||||
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="5" maxY="0.0"/>
|
||||
<state key="normal" title="Download Recording">
|
||||
<color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</state>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
|
||||
<integer key="value" value="25"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
</userDefinedRuntimeAttributes>
|
||||
<connections>
|
||||
<action selector="downloadRecording:" destination="9gy-Qq-XHU" eventType="touchUpInside" id="dRc-xY-3VG"/>
|
||||
<action selector="playNowBtnTapped:" destination="fax-bi-Mb9" eventType="touchUpInside" id="abN-kZ-2DQ"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
</stackView>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Nuf-TE-5LZ">
|
||||
<rect key="frame" x="10" y="63" width="45" height="45"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="45" id="Llr-tl-LTD"/>
|
||||
<constraint firstAttribute="height" constant="45" id="SlF-Fz-fuA"/>
|
||||
</constraints>
|
||||
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
|
||||
<state key="normal" image="BackArrow"/>
|
||||
<connections>
|
||||
<action selector="backBtnTapped:" destination="9gy-Qq-XHU" eventType="touchUpInside" id="oPW-n3-xjb"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="fhs-yV-fEZ"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
<constraints>
|
||||
<constraint firstItem="lIa-J9-DOG" firstAttribute="centerY" secondItem="fhs-yV-fEZ" secondAttribute="centerY" constant="200" id="2hX-z2-lum"/>
|
||||
<constraint firstAttribute="trailing" secondItem="lIa-J9-DOG" secondAttribute="trailing" constant="20" id="7WR-NN-0zd"/>
|
||||
<constraint firstItem="Nuf-TE-5LZ" firstAttribute="top" secondItem="fhs-yV-fEZ" secondAttribute="top" constant="15" id="7jB-UT-Xqq"/>
|
||||
<constraint firstItem="Nuf-TE-5LZ" firstAttribute="leading" secondItem="fhs-yV-fEZ" secondAttribute="leading" constant="10" id="Tcl-yf-D2V"/>
|
||||
<constraint firstItem="lIa-J9-DOG" firstAttribute="leading" secondItem="fhs-yV-fEZ" secondAttribute="leading" constant="20" id="yH2-iw-QCt"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="backButton" destination="Nuf-TE-5LZ" id="syG-po-xEv"/>
|
||||
<outlet property="downloadRecordingBtn" destination="3hw-ks-w1o" id="bWR-kY-K1G"/>
|
||||
<outlet property="outerStack" destination="lIa-J9-DOG" id="uBJ-C7-rCI"/>
|
||||
<outlet property="playBtn" destination="mHM-7B-XXx" id="qso-dg-U5b"/>
|
||||
<outlet property="startRecordBtn" destination="yv1-I1-YGZ" id="Xf6-hb-4Qd"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="s0j-fC-RFe" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="2443" y="-27"/>
|
||||
</scene>
|
||||
<!--Testing KaraokeVC-->
|
||||
<scene sceneID="ISg-0i-f5X">
|
||||
<objects>
|
||||
<viewController storyboardIdentifier="TestingKaraokeVC" id="Ixs-R0-bqs" customClass="TestingKaraokeVC" customModule="WOKA" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="SEn-mb-vM4">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" distribution="fillEqually" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="ApB-WQ-WAf">
|
||||
<rect key="frame" x="20" y="712" width="374" height="50"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="tailTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="K2c-bI-iaW" customClass="LocalisedElementsButton" customModule="WOKA" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="177" height="50"/>
|
||||
<color key="backgroundColor" red="1" green="0.044904738159999999" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="displayP3"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="50" id="uXi-mu-IQ8"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" name="Exo2-Bold" family="Exo 2" pointSize="14"/>
|
||||
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<inset key="titleEdgeInsets" minX="5" minY="0.0" maxX="0.0" maxY="0.0"/>
|
||||
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="5" maxY="0.0"/>
|
||||
<state key="normal" title="Start Recording" image="Microphone"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
|
||||
<integer key="value" value="25"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
</userDefinedRuntimeAttributes>
|
||||
<connections>
|
||||
<action selector="startButtonPressed:" destination="Ixs-R0-bqs" eventType="touchUpInside" id="zGF-yq-0o2"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="249" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="tailTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="srX-WU-cle" customClass="LocalisedElementsButton" customModule="WOKA" customModuleProvider="target">
|
||||
<rect key="frame" x="197" y="0.0" width="177" height="50"/>
|
||||
<color key="backgroundColor" name="TextDarkBlue"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="50" id="wwk-CU-vet"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" name="Exo2-Bold" family="Exo 2" pointSize="16"/>
|
||||
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<inset key="titleEdgeInsets" minX="5" minY="0.0" maxX="0.0" maxY="0.0"/>
|
||||
<inset key="imageEdgeInsets" minX="-10" minY="0.0" maxX="10" maxY="0.0"/>
|
||||
<state key="normal" title="Play" image="PlayButtonSmall"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
|
||||
<integer key="value" value="25"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
</userDefinedRuntimeAttributes>
|
||||
<connections>
|
||||
<action selector="stopButtonPressed:" destination="Ixs-R0-bqs" eventType="touchUpInside" id="TlD-dh-StE"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="t9O-c6-dcl"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
<constraints>
|
||||
<constraint firstItem="ApB-WQ-WAf" firstAttribute="leading" secondItem="SEn-mb-vM4" secondAttribute="leading" constant="20" id="aa2-wO-FVT"/>
|
||||
<constraint firstItem="t9O-c6-dcl" firstAttribute="trailing" secondItem="ApB-WQ-WAf" secondAttribute="trailing" constant="20" id="cV0-l6-keq"/>
|
||||
<constraint firstItem="t9O-c6-dcl" firstAttribute="bottom" secondItem="ApB-WQ-WAf" secondAttribute="bottom" constant="100" id="cxI-Ia-Ggd"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="2b3-eU-9bD" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="3207" y="-27"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="BackArrow" width="21.333333969116211" height="21.333333969116211"/>
|
||||
<image name="CloseIconEmpty" width="30" height="30"/>
|
||||
<image name="FavouriteRemove" width="42.5" height="42.5"/>
|
||||
<image name="LikeRemove" width="42.5" height="42.5"/>
|
||||
|
||||
Reference in New Issue
Block a user