Dev1an / Swift-Atem

Blackmagic Design Atem network protocol implementation in swift 5.1 using NIO 2
MIT License
58 stars 27 forks source link

Embed inside macOS iOS app #6

Closed tfe closed 4 years ago

tfe commented 4 years ago

Hi, I was trying this out and so far have been unable to connect to an ATEM. The only reason I can think of is that it's on a different subnet (10.10.0.x) and I've only been able to try it from another subnet (10.20.0.x). Traffic is routed between the two, and the native ATEM control software can connect just fine.

Do you know of any reason that shouldn't work? Has this project been used with a real ATEM recently? I wouldn't think it would matter in terms of just being able to connect, but the ATEM in question is on firmware 7.4, a little behind current.

By "not connect" I mean it executes the the handler code but it never receives any events, e.g. InitiationComplete or PreviewBusChanged, nor does it respond when I call send(message:) on the controller instance.

I feel like I must be missing something obvious since the example code is quite straightforward. I'll paste what I have below for reference.

I definitely appreciate any insight you might have!

import UIKit
import Atem

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        do {
            let controller = try Controller(ipAddress: "10.10.0.20") { handler in
                print("in handler") // this prints, nothing else below does

                handler.when { (version: ProtocolVersion) in
                    print(version)
                }

                handler.when { (change: InitiationComplete) in
                    print("Initiation complete")
                    print(change)
                }

                handler.when { (change: SourceTallies) in
                    print(change)
                }
                handler.when{ (change: PreviewBusChanged) in
                    print(change) // prints: 'Preview bus changed to input(x)'
                }
            }

            controller.send(message: ChangePreviewBus(to: .black))

        } catch let error {
            debugPrint(error)
        }
    }
}
Dev1an commented 4 years ago

This is caused because of Automatic reference counting. When the viewDidLoad function executes it will create a controller but immediately destroys it because after the viewDidLoad function finishes it has no more references to controller and hence deletes the atem controller. Make the controller an instance variable of your UIViewController instead of a local variable inside viewDidLoad func and things should work.

tfe commented 4 years ago

Hi, thanks for the note but I'm afraid that's not it. 😕 You can see in the snippet above I send a message to the switcher before viewDidLoad returns, and that doesn't go through (controller's handler.connectionState is nil at that point, though optional chaining hides this).

I tried making the controller an instance variable anyway, but no change in behavior.

I also tried as a Mac app too, just to see, but got the same thing.

The different subnet theory or the ATEM firmware theories are the best ideas I have at this point. I'll be able to try connecting from the same subnet this weekend.

Dev1an commented 4 years ago

OK, that's odd. I have tested this library with real ATEM's (TV-Studio and Production studio 4K) and it does work on my end.

I'm not so sure what differences there are between Version 7.4 and 7.3. But the commits from before 12 december 2018 definitely worked on an atem with 7.3. Since 0eace2ec946130089f342c475486a80f987cab2d I used the library with version 7.5.2, but I never tried it on 7.4. For the moment I use my ATEM's on version 8.2.1. If I have some time I will try to downgrade to 7.4 to try that out.

Let me know if it works when you are on the same subnet. And if you can share some code of your application that does not work, maybe that could help too.

Dev1an commented 4 years ago

You can see in the snippet above I send a message to the switcher before viewDidLoad returns, and that doesn't go through (controller's handler.connectionState is nil at that point, though optional chaining hides this).

When you call controller.send inside your viewDidLoad it is not guaranteed that the actual UDP message is sent right away. The message gets queued and it is only sent after a couple of milliseconds (to leave the possibility to send it together with other pending packets). So it could have been that the controller was already teared down before the queue was flushed.

tfe commented 4 years ago

OK, when I test this weekend I will give 0eace2e a try. I can't really change FW versions on this ATEM, so thanks for being willing to try that on yours.

Here's the code I tried (same as before, just with the controller pulled out into an instance variable). I also tried a version where the controller was a static let on the view controller, and constructed outside of viewDidLoad.

import Cocoa
import Atem

class ViewController: NSViewController {

    var controller: Controller?

    override func viewDidLoad() {
        super.viewDidLoad()

        do {
            self.controller = try Controller(ipAddress: "10.10.0.20") { handler in
                print("in handler")

                handler.when { (version: ProtocolVersion) in
                    print(version)
                }

                handler.when { (change: InitiationComplete) in
                    print("Initiation complete")
                    print(change)
                }

                handler.when { (change: SourceTallies) in
                    print(change)
                }
                handler.when{ (change: PreviewBusChanged) in
                    print(change) // prints: 'Preview bus changed to input(x)'
                }
            }

            controller!.send(message: ChangePreviewBus(to: .black))

        } catch let error {
            debugPrint(error)
        }
    }
}
tfe commented 4 years ago

Unfortunately no luck on that older commit, or being on the same subnet. 😕 I also tried running a packet capture to see what traffic was actually being passed between my computer and the switcher, and there was nothing at all. I see plenty of traffic between the switcher and other computers/panels, and I can see traffic between it and my computer when using ATEM Control, but nothing when running my test project.

Please let me know if you had any luck on your end, or if you have any ideas on how to troubleshoot further.

I've poked around a little in the source, specifically the initializer for Controller but it's hard for me to tell if things are working as normal or failing in there. Based on the packet capture it seems like even the initial connection to the switcher isn't going out successfully. I assume the architecture is that it has to connect/subscribe to the ATEM in order for the ATEM to start sending messages to it? I do know that channelRead and executeTimerTask are never called. But I haven't fully figured out the architecture of how the initial connection happens to poke around in there...

Anyway, any light you can shed would be helpful. I know your time is limited so I'm appreciative of any help you can give. 🙂

Dev1an commented 4 years ago

Hi I just tried the latest commit from master with an old Atem (1 M/E Production studio 4K) running the 7.4 software and everything seems to work fine.

Dev1an commented 4 years ago

What happens if you checkout bd73571c83849efff6b898adf08230cd7732af40 and execute swift run PreviewSwitcher in the terminal? Or swift run VersionDump

Do the above sample programs produce any output in the terminal?

tfe commented 4 years ago

Works! Successfully connects, dumps the current state of all the MEs, then I can successfully make preview selections from there. What's different about how it works inside a macOS or iOS project?

I tried updating my test macOS project to that commit and there was no change. I also tried copying the code from the PreviewSwitcher script verbatim into the app delegate didFinishLaunching method (had to use a try! so it would compile), still no luck.

Thanks for checking the 7.4 software! When you test, do you use those Swift scripts or do you actually have it integrated into a mac or iOS app inside an Xcode project?

Dev1an commented 4 years ago

OK. I only tested the PreviewSwitcher and VersionDump executables from inside Xcode. Embedding in it properly in a macOS app should yield the same results. For a working example of this library in a macOS app, take a look at the Simulator GUI.

Dev1an commented 4 years ago

It seems to me that you are not running into a problem with the library but that it’s rather a problem with your application. Are you making a sandboxed app or using te hardened runtime? Did you add the correct entitlements and or capabilities?

tfe commented 4 years ago

I have very little experience with iOS/Mac development so all I did was create a new project in Xcode. I did not realize I needed to grant network access explicitly... but that solved my problem immediately. 🤦 🤦 🤦

I apologize for the time and attention you spent on this unnecessarily. Thanks for being so patient.

I would like to ask that you look at #5 though, because the project won't build on iOS platforms right now due to that.