How should I add custom view over mobile vlc kit in iOS - ios

I want to show some custom control over vlc player,but when i am trying to do so, my control hides when actual video start running in player
player = VLCMediaPlayer()
player.media = VLCMedia(url: URL(string: "rtmp://сс.tv/sea")!)
player.drawable = view
let abc = uiview()
view.addsubview(abc)
I had tried to show uiview over my vlc player but failed to do so
player = VLCMediaPlayer()
player.media = VLCMedia(url: URL(string: "rtmp://сс.tv/sea")!)
player.drawable = view
I need to show some custom view whenever my video plays they should be visible , any help will pe appreciable

To add an overlay with custom controls you have to create a separate UIView which will hold the player, and create a controls UIView too. The first one should look something like this, with your own frame:
let root = UIView()
root.frame.size.height = UIScreen.main.bounds.width
root.frame.size.width = UIScreen.main.bounds.height
Now we can set "root" to be the main video rendering view for the player:
mediaPlayer.drawable = root
Create your custom controls view which will display on top of the player:
var child = UIHostingController(rootView: ControlsVLC())
child.view.frame.size.height = UIScreen.main.bounds.width
child.view.frame.size.width = UIScreen.main.bounds.height
child.view.backgroundColor = .clear // You will need to add this so you can see the video behind the controls
child.view.isOpaque = false
Lastly, you have to add them as subviews accordingly so the controls will display on top of the video:
self.addSubview(root)
self.addSubview(child.view)
self.bringSubviewToFront(child.view)
self.sendSubviewToBack(root) // Not sure if necessary but works
Be aware that this was done with SwiftUI on iOS 16.
Your final code should look something like below:
class PlayerUIView: UIView, VLCMediaPlayerDelegate {
var mediaPlayer = VLCMediaPlayer()
override init(frame: CGRect) {
super.init(frame: frame)
let root = UIView()
root.frame.size.height = UIScreen.main.bounds.width
root.frame.size.width = UIScreen.main.bounds.height
let url = URL(string: "your.source")!//replace your resource here
mediaPlayer.media = VLCMedia(url: url)
mediaPlayer.delegate = self
mediaPlayer.drawable = root
mediaPlayer.play()
var child = UIHostingController(rootView: ControlsVLC())
child.view.frame.size.height = UIScreen.main.bounds.width
child.view.frame.size.width = UIScreen.main.bounds.height
child.view.backgroundColor = .clear
child.view.isOpaque = false
self.addSubview(root)
self.addSubview(child.view)
self.bringSubviewToFront(child.view)
self.sendSubviewToBack(root)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
}
}
This worked for me.

Related

AVPlayer video disappears when navigating 'back' in Swift

When navigating to a subview, I have set it up so that a video plays automatically. At the bottom of the video, there is a group of links that go to related content. When clicking one of them, a new view is pushed onto the stack and a different video starts playing.
The problem happens when using the automatically generated '< Back' button to go back to the prior view (which had a different video). This original view can be operated using the player controls, but nothing shows up on the screen.
I've tried to update the CGRect frame, use onAppear to reinitialize the video player, and also followed the advice here.
So far nothing seems to work. Here is the code I am using for the actual video player (adapted from Chris Mash's website):
import SwiftUI
import AVKit
import UIKit
import AVFoundation
let playerLayer = AVPlayerLayer()
class PlayVideo: UIView {
init(frame: CGRect, url: URL) {
super.init(frame: frame)
// Create the video player using the URL passed in.
let player = AVPlayer(url: url)
player.volume = 100 // Will play audio if you don't set to zero
player.play() // Set to play once created
// Add the player to our Player Layer
playerLayer.player = player
playerLayer.videoGravity = .resizeAspectFill // Resizes content to fill whole video layer.
playerLayer.backgroundColor = UIColor.black.cgColor
layer.addSublayer(playerLayer)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
override func layoutSubviews() {
super.layoutSubviews()
playerLayer.frame = bounds
}
static func pauseVideo() {
playerLayer.player?.pause()
}
}
struct ViewVideo: UIViewRepresentable {
var videoURL:URL
var previewLength:Double?
func makeUIView(context: Context) -> UIView {
return PlayVideo(frame: .zero, url: videoURL)
}
func updateUIView(_ uiView: UIView, context: Context) {
}
}
This is called from the main view using:
ViewVideo(videoURL: videoURL)
The only work around I can think of is to disable the back button and force the user to go back to the main view every time. That's a terrible option and I'm hoping someone will have some helpful advice here. Thanks -
If I understand this correctly, you play a different video, when you navigate to the new view. So you create a new PlayVideo view?
Then the problem is that your playerLayer is a static property. The new view will set a new player into the playerLayer and replace the old one. Similarly, if you pause one player, both are paused. Additionally, adding the player as a sublayer to the new view will remove it from the old view.
You need the playerLayer as a local property to your view. Or at least a AVPlayerLayer for every video you want to play. Then you need a mechanism for pausing/restarting each video, when it becomes visible. For example by implementing the viewWillAppear. This method gets always called, when you navigate back to a view/ the view becomes visible.
Thank you to #dominik-105 for helping with this question. I was able to fix the problem using the suggestions that were made.
Specifically, I removed the global definition of playerLayer and instead placed it as a local variable in my main view call:
var playerLayer = AVPlayerLayer()
I then call ViewVideo with the playerLayer: playerLayer tag, and the ViewVideo then calls PlayVideo with playerLayer:AVPlayerLayer as part of the init.
Interestingly, this leads to problems with the override layout function I was using to define the size of the video box. I define the frame directly in the init now and removed the old override function code. The full code is now:
import SwiftUI
import AVKit
import UIKit
import AVFoundation
class PlayVideo: UIView {
init(frame: CGRect, url: URL, playerLayer: AVPlayerLayer, width: CGFloat, height: CGFloat) {
super.init(frame: frame)
// Create the video player using the URL passed in.
let player = AVPlayer(url: url)
player.volume = 100 // Will play audio if you don't set to zero
player.play() // Set to play once created
// Add the player to our Player Layer
playerLayer.player = player
playerLayer.videoGravity = .resizeAspectFill // Resizes content to fill whole video layer.
playerLayer.backgroundColor = UIColor.black.cgColor
playerLayer.player?.actionAtItemEnd = .pause
layer.addSublayer(playerLayer)
playerLayer.frame = CGRect(x: 0, y: 0, width: width, height: height)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
}
struct ViewVideo: UIViewRepresentable {
var videoURL:URL
var playerLayer: AVPlayerLayer
var width: CGFloat
var height: CGFloat
func makeUIView(context: Context) -> UIView {
return PlayVideo(frame: .zero, url: videoURL, playerLayer: playerLayer, width: width, height: height)
}
func updateUIView(_ uiView: UIView, context: Context) {
}
}
I use GeometryReader to define the size of the box, and then pass along the width and height to the struct, which passes it along to the class.

ASNetworkImageNode gif not loading

I am creating custom node to show gif file with play and pause button.
Following is my code.
import Foundation
import AsyncDisplayKit
class GifNode: ASCellNode {
var gifImageNode:ASNetworkImageNode = {
let node = ASNetworkImageNode()
node.contentMode = .scaleAspectFit
node.shouldRenderProgressImages = true
return node
}()
var playImage: ASImageNode = {
let node = ASImageNode()
node.contentMode = .scaleAspectFit
node.style.height = ASDimensionMakeWithPoints(30)
node.style.height = ASDimensionMakeWithPoints(30)
node.backgroundColor = .gray
node.image = #imageLiteral(resourceName: "play")
return node
}()
init(model:GifContent)
{
super.init()
self.automaticallyManagesSubnodes = true
let width = UIScreen.main.bounds.size.width
let height = (width * model.height) / model.width
gifImageNode.url = URL(string: "https://i.pinimg.com/originals/07/44/38/074438e7c75034df2dcf37ba1057803e.gif")
gifImageNode.style.width = ASDimensionMake(width)
gifImageNode.style.height = ASDimensionMake(height)
gifImageNode.animatedImagePaused = true
gifImageNode.addTarget(self, action: #selector(self.toggleGifPlay), forControlEvents: .touchUpInside)
}
#objc func toggleGifPlay()
{
self.gifImageNode.animatedImagePaused = !self.gifImageNode.animatedImagePaused
self.playImage.isHidden = !self.gifImageNode.animatedImagePaused
}
override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec {
let playButtonCenterSpec = ASCenterLayoutSpec(centeringOptions: .XY, sizingOptions: .minimumXY, child: self.playImage)
return ASOverlayLayoutSpec(child: gifImageNode, overlay: playButtonCenterSpec)
}
}
When I try to use gif node in another ASCellNode it does not render. I can see play button but not the actual gif file. If I try to load jpeg with same control, it works fine. Also it works fine, if I try to load gif directly and not using above class.
Not sure if I am missing anything.
I test your app, but gifImageNode.animatedImagePaused = true is disabled by default ur animation.

Embedding an AVPlayerViewController into a UIView

I am trying to play a video inside a UIView, but the video player always appears outside of the UIView which I am embedding the AVPlayerViewController into.
Below is what I have so far....
class ViewController: UIViewController {
let smallVideoPlayerViewController = AVPlayerViewController()
#IBOutlet weak var videoView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
let myFileManager = FileManager.default
let mainBundle = Bundle.main
let resourcesPath = mainBundle.resourcePath!
guard let allItemsInTheBundle = try? myFileManager.contentsOfDirectory(atPath: resourcesPath) else {
return
}
let videoName = "test"
let videoPath = Bundle.main.path(forResource: videoName, ofType: "mp4")
let videoUrl = URL(fileURLWithPath: videoPath!)
smallVideoPlayerViewController.showsPlaybackControls = false
smallVideoPlayerViewController.player = AVPlayer(url: videoUrl)
videoView.addSubview(smallVideoPlayerViewController.view)
smallVideoPlayerViewController.view.frame = videoView.frame
smallVideoPlayerViewController.player?.play()
}
...
}
The background colour of UIView is set to white. As seen from the screenshot, AVPlayer is outside of the UIView.
I tried manually setting the dimensions and position of the AVPlayer, but no luck with that either.
I am using Xcode 9.2. The project has not warnings regarding to any layout issues.
How can I perfectly align the AVPlayer, so that is would appear inside the UIView.
Thanks
Change this line:
smallVideoPlayerViewController.view.frame = videoView.frame
to this:
smallVideoPlayerViewController.view.frame = videoView.bounds
The reason is because videoView.frame includes the origin in its superview which you don't want. videoView.bounds is just the size with an origin of 0,0.
Of course you might want to go further set the auto-resizing mask to keep the video player frame the same like this:
smallVideoPlayerViewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
or even to go full blown auto-layout.

AVPlayer not full screen in landscape mode using iOS swift

I have a video view set to full screen. However while playing in the simulator, it is not running full screen.
This issue is only for iPads and not iPhones.
Here is my code:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
let bundle: Bundle = Bundle.main
let videoPlayer: String = bundle.path(forResource: "normalnewer", ofType: "mp4")!
let movieUrl : NSURL = NSURL.fileURL(withPath: videoPlayer) as NSURL
// var fileURL = NSURL(fileURLWithPath: "/Users/Mantas/Desktop/123/123/video-1453562323.mp4.mp4")
playerView = AVPlayer(url: movieUrl as URL)
NotificationCenter.default.addObserver(self,selector: #selector(playerItemDidReachEnd),name: NSNotification.Name.AVPlayerItemDidPlayToEndTime,object: self.playerView.currentItem) // Add observer
var playerLayer=AVPlayerLayer(player: playerView)
// self.avPlayerLayer.playerLayer = AVLayerVideoGravityResizeAspectFill
playerLayer.frame = viewVideo.bounds
// self.playerLayer.frame = self.videoPreviewLayer.bounds
viewVideo.layer.addSublayer(playerLayer)
playerView.play()
UserDefaults.standard.set("normalnewer", forKey: "video")
}
I have tried checking landscape mode in target's general.
Gone through the threads below:
how to play a video in fullscreen mode using swift ios?
Play video fullscreen in landscape mode, when my entire application is in lock in portrait mode
Setting device orientation in Swift iOS
But these did not resolve my issue.
Here is a screenshot.
Can somebody help me resolve this?
I see you solved your issue, but I noticed you were using an AVPlayerLayer. The orientation rotation is handled with the AVPlayerViewController, but not with a custom view using a player layer. It is useful to be able to put the layer in fullscreen without rotating the device. I answered this question elsewhere, but I will put it here as well.
Transforms and frame manipulation can solve this issue:
extension CGAffineTransform {
static let ninetyDegreeRotation = CGAffineTransform(rotationAngle: CGFloat(M_PI / 2))
}
extension AVPlayerLayer {
var fullScreenAnimationDuration: TimeInterval {
return 0.15
}
func minimizeToFrame(_ frame: CGRect) {
UIView.animate(withDuration: fullScreenAnimationDuration) {
self.setAffineTransform(.identity)
self.frame = frame
}
}
func goFullscreen() {
UIView.animate(withDuration: fullScreenAnimationDuration) {
self.setAffineTransform(.ninetyDegreeRotation)
self.frame = UIScreen.main.bounds
}
}
}
Setting the frame of the AVPlayerLayer changes it's parent's frame. Save the original frame in your view subclass, to minimize the AVPLayerLayer back to where it was. This allows for autolayout.
IMPORTANT - This only works if the player is in the center of your view subclass.
Incomplete example:
class AVPlayerView: UIView {
fileprivate var avPlayerLayer: AVPlayerLayer {
return layer as! AVPlayerLayer
}
fileprivate var hasGoneFullScreen = false
fileprivate var isPlaying = false
fileprivate var originalFrame = CGRect.zero
func togglePlayback() {
if !hasGoneFullScreen {
originalFrame = frame
hasGoneFullScreen = true
}
isPlaying = !isPlaying
if isPlaying {
avPlayerLayer.goFullscreen()
avPlayerLayer.player?.play()
} else {
avPlayerLayer.player?.pause()
avPlayerLayer.minimizeToFrame(originalFrame)
}
}
}
After spending some time taking a good look at the Storyboard, I tried changing the fill to Aspect fill & fit and that resolved the problem :)
The best thing you can do is put this code
In viewDidLayoutSubviews and enjoy it!
DispatchQueue.main.async {
self.playerLayer?.frame = self.videoPlayerView.bounds
}

Passing variable from UIViewController to SK Scene

I'm making an a game in XCode 7 using Swift 2. I have a variable that I want to pass from the start screen (which is an UIViewController) to the game scene (which is an SKScene). I want the player to select a character in a UIView and play with it in the SKScene. I also want the score that's in the SKScene to show in the game-over screen that's an UIView. I've seen tutorials for passing data between two UIViewControllers and between two SKScenes, but none of them work for this case.
How can I pass a variable from an UIViewController to a SKScene (and vice versa)?
I ran over this same problem yesterday.
Swift 5.2, xcode 12 and targeting iOS 14. Searched high and low. Eventually found the userData attribute of the SKScene.
Note: The app is based on SwiftUI and the SKScene is inside a UIViewRepresentable:
struct SpriteKitContainer: UIViewRepresentable {
typealias UIViewType = SKView
var skScene: SKScene!
#Binding var timerData : ModelProgressTimer
Not that I am passing a binding into the View - this contains a number of data items I wanted to make available to the scene.
I placed code in two places to populate skScene.userData:
func makeUIView(context: Context) -> SKView {
self.skScene.scaleMode = .resizeFill
self.skScene.backgroundColor = .green
debugPrint("setting user data")
self.skScene.userData = [:]
self.skScene.userData!["color"] = UIColor(timerData.flatColorTwo!)
self.skScene.userData!["running"] = timerData.isRunning
self.skScene.userData!["percent"] = timerData.percentComplete
let view = SKView(frame: .zero)
view.preferredFramesPerSecond = 60
// view.showsFPS = true
// view.showsNodeCount = true
view.backgroundColor = .clear
view.allowsTransparency = true
return view
}
func updateUIView(_ view: SKView, context: Context) {
self.skScene.userData = [:]
self.skScene.userData!["running"] = timerData.isRunning
self.skScene.userData!["color"] = UIColor(timerData.flatColorTwo!)
self.skScene.userData!["percent"] = timerData.percentComplete
view.presentScene(context.coordinator.scene)
}
Then, inside the skScene object, I am able to retrieve the userData values:
var item: SKShapeNode! = nil
override func sceneDidLoad() {
let scaleUp = SKAction.scale(by: 2.0, duration: 1.0)
let scaleDown = SKAction.scale(by: 0.5, duration: 1.0)
scaleUp.timingMode = .easeInEaseOut
scaleDown.timingMode = .easeInEaseOut
let sequence = SKAction.sequence([scaleUp,scaleDown])
actionRepeat = SKAction.repeatForever(sequence)
item = SKShapeNode(circleOfRadius: radius)
addChild(item)
}
override func didChangeSize(_ oldSize: CGSize) {
item.fillColor = self.userData!["color"] as! UIColor
item.strokeColor = .clear
let meRunning = self.userData!["running"] as! Bool
if meRunning {
item.run(actionRepeat)
} else {
item.removeAllActions()
}
}
This draws a circle that "pulses" if the "running" boolean is set in the userdata (a Bool value).
If you haven't already found the answer, I did find a way to do this. I was doing something very similar.
In my case I have a UIView that gets called as a pause menu from my scene. I have made the class and variable names a little more ambiguous so they can apply to your situation as well.
class MyView: UIView {
var scene: MyScene!
var button: UIButton!
init(frame: CGRect, scene: MyScene){
super.init(frame: frame)
self.scene = scene
//initialize the button
button.addGestureRecognizer(UITapGestureRecognizer(target: self, action: "buttonTapped"))
}
func buttonTapped(){
self.removeFromSuperview() // this removes my menu from the scene
scene.doTheThing() // this calls the method on my scene
}
Here are the relevant parts of my scene.
class MyScene: SKScene {
func doTheThing(){
//this is a function on the scene. You can pass any variable you want through the function.
}
}
In your situation, it sounds like the first screen is a UIView and the second screen is the SKScene.
You may want to make your SKScene first, pause it, and then add the UIView in front of the Scene. Once the character is selected, you can remove the UIView and add your character into the scene.
I hope that this answers your question. If it doesn't let me know.
From the level select scene:
let scene = GameScene(fileNamed:"GameScene")
scene?.userData = [:]
scene?.userData!["level"] = 21
self.view?.presentScene(scene!)
From within the game scene:
let level = self.userData!["level"] as! Int

Resources