I am porting a showroom application from as3/starling to a native swift iPad app.
I have 2 questions:
How can I fade a video over my spritekit content (from alpha 0 to 1).
How to control the iPad volume with an individual UI element without showing the iOS onscreen-volume notification graphic.
2 Answers
Answers 1
Here is a full project solution for you: https://github.com/fluidityt/BountyVideoFade
(Note, the simulator I couldn't get to show the video (just audio). Use a real device when testing if you have that problem.)
When the game loads, the movie starts at alpha 0 and fades in to 1 over a few seconds.
To mute the volume, hold your finger on the screen; to set the volume to max, release your finger from the screen. The gray iOS volume bar does not show up :)
GameViewController.swift:
import MediaPlayer import SpriteKit // Global access to our gameViewController (not a best practice but effective for testing). var gGVC = GameViewController() extension UIViewController { /* extension is property of Senseful on SO: https://stackoverflow.com/a/44480864/6593818 */ func setVolumeStealthily(_ volume: Float) { guard let view = viewIfLoaded else { assertionFailure("The view must be loaded to set the volume with no UI") return } let volumeView = MPVolumeView(frame: .zero) guard let slider = volumeView.subviews.first(where: { $0 is UISlider }) as? UISlider else { assertionFailure("Unable to find the slider") return } volumeView.clipsToBounds = true view.addSubview(volumeView) DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1) { [weak slider, weak volumeView] in slider?.setValue(volume, animated: false) DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1) { [weak volumeView] in volumeView?.removeFromSuperview() } } } } class GameViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() gGVC = self // Registers our global to this instance. // Other default stuff goes below: // ...
GameScene.swift:
class GameScene: SKScene { let videoNode = SKVideoNode(fileNamed: "sample.mp4") override func didMove(to view: SKView) { removeAllChildren() // Delete this in your actual project. addChild(videoNode) videoNode.alpha = 0 videoNode.play() videoNode.run(.fadeIn(withDuration: 5)) } override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { gGVC.setVolumeStealthily(0) } override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) { gGVC.setVolumeStealthily(1) } }
Answers 2
My first answer answers your initial question, and this answer is tailored to the subsequent questions that were posted in the comments. I feel that both answers are useful to the community, instead of combining into just one which I think would be confusing.
Here I implement a volume slider and a scrubbing slider that controls the VideoNode's AVPlayer object.. you get the full power of the AVPlayer but with the convenience of the SKVideoNode. Also, the volume for playing video is controlled without having to adjust the system volume and subsequent gray sound box pop-up:
import SpriteKit import MediaPlayer class GameScene: SKScene { let seekSlider = UISlider(frame: CGRect(x: 0, y: 0, width: 200, height: 15)) let volSlider = UISlider(frame: CGRect(x: 0, y: 0, width: 200, height: 15)) // Because it takes time for us to load the video, we don't know it's duration, // hence we don't know what the `.maximumValue` should be for the seekSlider let defaultMax = Float(0.123456789) var player: AVPlayer! var videoNode: SKVideoNode! func seekSliderChangedValue(sender: UISlider) { // Wait for file to finish loading so we can get accurate end duration: if sender.maximumValue == defaultMax { if CMTimeGetSeconds(player.currentItem!.duration).isNaN { return } else { sender.maximumValue = Float(CMTimeGetSeconds(player.currentItem!.duration)) } } let value = CMTimeMake(Int64(seekSlider.value), 1) player.seek(to: value) player.play() } func volSliderChangedValue(sender: UISlider) { player.volume = sender.value } override func didMove(to view: SKView) { removeAllChildren() // Delete this in your actual project. // Add some labels: let seekLabel = SKLabelNode(text: "Seek") seekLabel.setScale(3) seekLabel.verticalAlignmentMode = .center seekLabel.position = CGPoint(x: frame.minX + seekLabel.frame.width/2, y: frame.maxY - seekLabel.frame.height) let volLabel = SKLabelNode(text: "Volume") volLabel.setScale(3) volLabel.verticalAlignmentMode = .center volLabel.position = CGPoint(x: frame.minX + volLabel.frame.width/2, y: frame.minY + volLabel.frame.height + volSlider.frame.height) // Make player and node: let url = Bundle.main.url(forResource: "sample", withExtension: "mp4")! player = AVPlayer(url: url) videoNode = SKVideoNode(avPlayer: player!) //Configure seek slider: seekSlider.addTarget(self, action: #selector(seekSliderChangedValue), for: UIControlEvents.valueChanged) seekSlider.maximumValue = defaultMax let seekOrigin = convertPoint(toView: CGPoint(x: seekLabel.frame.minX, y: seekLabel.frame.minY)) seekSlider.frame = CGRect(origin: seekOrigin, size: CGSize(width: 200, height: 15)) //Configure vol slider: volSlider.addTarget(self, action: #selector(volSliderChangedValue), for: UIControlEvents.valueChanged) volSlider.value = 1 let volOrigin = convertPoint(toView: CGPoint(x: volLabel.frame.minX, y: volLabel.frame.minY)) volSlider.frame = CGRect(origin: volOrigin, size: CGSize(width: 200, height: 15)) //Scene stuff: view.addSubview(seekSlider) view.addSubview(volSlider) addChild(seekLabel) addChild(volLabel) addChild(videoNode) // Start video and animation: videoNode.alpha = 0 videoNode.play() videoNode.run(.fadeIn(withDuration: 5)) } }
0 comments:
Post a Comment