0

How do I trigger a segued iOS AVPlayer to start and stop playing (using Swift 2.2)?

My Watch Connectivity is sending "start" and "stop" message commands. How do I code the functions here to respond and react with the player?

The Watch connectivity messages are working correctly to play audio with AudioPlayer in a different view controller. I'm now trying to adapt this to control a video.

    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    let destination = segue.destinationViewController as!
    AVPlayerViewController
    let url = NSURL(string: self.specimen.filmMovieLink)
    destination.player = AVPlayer(URL: url!)
}

// WATCH CONNECTIVITY MESSAGE TO TRIGGER START AND STOP VIDEO
    func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void) {
        var replyValues = Dictionary<String, AnyObject>()
        switch message["command"] as! String {
        case "start" :
            //play video - correct here
           player.Play()
           replyValues["status"] = "Playing"
        case "stop" :
            //stop video - correct here
            player.stopPlay()
            replyValues["status"] = "Stopped"
       case "volume" :
            let level = message["level"] as! Float
            //player.adjustVolume(level)
            replyValues["status"] = "Vol = \(level)"
        default:
            break
       }
       replyHandler(replyValues)
    }
}

I am getting the error messages "use of unresolved identifier 'player' on the lines player.Play() and player.StopPlay(). I have tried replacing 'player' with 'AVPlayer', 'destination', and 'AVPlayerViewController' but it doesn't fix it.

Dimitri T
  • 921
  • 1
  • 13
  • 27

2 Answers2

3

I could post an example showing how you could "fix" your existing code, but it wouldn't do you any favors. The way you're currently trying to implement this is very convoluted, and it would benefit your app to rethink how data could be passed between different parts of your app.

A view controller is intended to control its own views. It shouldn't be trying to control something in a different controller which it presented (via the segue).

  • I would not recommend passing an AVPlayer object from the presenting (source) view controller to the presented (destination) view controller. Instead you could simply pass the url in prepareForSegue.

  • This would allow the destination view controller to instantiate its own AVPlayer, and let the player be encapsulated within that view controller. In keeping with encapsulation, no other view controller should need to know about (or try to interact) with that object.

Regarding "controlling" the player from the watch session, what I would suggest is setting up an app-wide session manager. This moves the session responsibility out from the view controller (where it really doesn't belong), and eliminates the complexity of trying to let one (inactive, source) view controller handle WCSession messages on behalf of another (active, destination) view controller.

  • There are several posts, as well as internet tutorials that discuss how to setup a WCSession manager.

  • Separating the functionality into separate modules also makes it easier to maintain the code (and lets you inject or mock different portions of the code while unit testing).

If you redesign your app in this manner, you could then use notifications to accomplish what you want. For example, your session manager could post the appropriate notification based on the message that it receives from the watch, and your AVPlayer view controller could observe and react to "start", "stop", and "volume" notifications.

Are there other options?

If you simply wanted to get the convoluted approach to work, you could try setting up a delegate (letting the second view controller delegate control of its player to the first view controller), or having the first view controller maintain a reference to the second view controller, so it could directly manipulate its player.

But keep in mind that you (or someone else) will have to maintain this code down the road. Any time you try to save now by "cutting corners," or using "quick and dirty hacks," will be wasted later. Don't be surprised to find yourself horrified at old code you (or someone else) wrote, or trying to remember/understand why you/they chose to do it that way, and hoping you don't inadvertently break things because of all the convolution.

Community
  • 1
  • 1
  • thank you, I am looking into this, very helpful. The idea of passing the url through to a view controller for the AV player is a good one, I will try that now. This coding is for a prototype (for sample / testing purposes) so a 'quick and dirty hack' by cutting corners is still of value (as a business decision, this is a case where when 'stuck' there is value in prototyping first with the quick and dirty solution /workaround, while I look into your wise suggestions for more sustainable solutions). I will try the code in watch session manager & passing the url over to another VC. Thanks again – Dimitri T Aug 17 '16 at 21:18
  • No problem! While there *is* value in prototyping, keep in mind that technical debt adds up fast! Quick and dirty code often unexpectedly makes it into the shipping version, because people get too busy implementing other features, and run out of time to also rewrite the hack. "Oh, well, we'll properly take care of that in the *next* version." And then they have to deal with bugs/crashes in the shipped app because of memory leaks, race conditions, running on faster/slower devices/networks, etc. –  Aug 17 '16 at 23:00
-1

Answering the more simple part of question of what function will trigger play from the segue (as an autoplay in effect), I've solved it that the code is:

destination.player!.play()

I had tried destination.play() and player.play() which didn't work, as the function needs to be destination.player!.play(), as shown here:

  override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
   let destination = segue.destinationViewController as!
   AVPlayerViewController
   let url = NSURL(string: self.specimen.filmMovieLink)
   destination.player = AVPlayer(URL: url!)
   destination.player!.play()

}

Calling 'destination.player!.play()' and 'destination.player!.pause()' in the watch connectivity function is not bringing any errors. However on pressing my watch button to send the 'start' message for 'destination.player!.play()', or pause message, my watch the app is crashing, so - as per @PetahChristian's answer/ wise advice, this seems be conflicting with other code, and the 'autoplay' only works as a quick and dirty solution.

Dimitri T
  • 921
  • 1
  • 13
  • 27
  • The `play()` method will be problematic. Consider that you're calling it at a point where the system is ***preparing*** for a segue to happen. The destination view controller may not have been presented yet, its view may not have been loaded yet, etc. If you want the video to "autoplay," a better way to do that is to have the destination view controller play it, *after* its view appears. –  Aug 17 '16 at 21:31