Using python, how do I construct a delegate for the macOS sound player NSSound?

159 Views Asked by At

I am writing a music database manager using python and tkinter. It is nearly all complete but I am stuck on playing songs from a list. The native macOS player NSSound has a built-in callback to a delegate which signals the end of the track so I would like to use that to trigger a virtual event back to the main app to send the next song to play. But I cannot figure out how make the callback work and would appreciate some help (this being just a hobby project which has got somewhat out of hand).

This skeleton code shows the relevant structure of the app and plays the selected file as expected, but none of my numerous attempts at formulating the delegate have worked. As I understand it the player appoints the Delegate class as its delegate, and then this class should have a callback method 'sound' to receive a message when the song ends. I have tried to figure it out from the Apple Developer protocol, and also searched extensively and found only one example.

How do I do get to the 'Success!' message please?

from AppKit import NSSound
import tkinter as tk
from tkinter import filedialog
       
class Delegate (NSSound):
    def init(self):
        self = super(Delegate, self).init()
        return self        
    #def sound (...) ???   #this should fire when the song finishes?
    #   print('Success!')
    
class App(tk.Tk):  #representing the rest of the music database app
    def __init__(self):
        super().__init__()         
        file = filedialog.askopenfilename()          
        song = NSSound.alloc()
        song.initWithContentsOfFile_byReference_(file, True) 
        delegate = Delegate.alloc().init()
        song.setDelegate_(delegate)      
        song.play()
           
if __name__ == "__main__":
    app = App()
    app.mainloop()
  
2

There are 2 best solutions below

6
valeCocoa On

The NSSound instance will call a method with this (swift) signature on its delegate:

sound(_:didFinishPlaying:)

Thus I think your Python function should have the same signature: it’s name should be sound and it should accept two parameters, the first one without a label (_) of type NSSound, the second one of type Bool with a label didFinishPlaying.

Your sound function in your delegate doesn’t appear to have such signature, hence most probably it’s not called.

That is cause such method on the delegate is optional, meaning if an instance conforming to NSSoundDelegate doesn’t implement such method, it won’t trap when attempted to be used by a delegating NSSound instance.

0
user10850186 On

Thanks for your help @valeCocoa, your perserverance helped a lot. This works:

from AppKit import NSSound
import tkinter as tk
from tkinter import filedialog
import objc

class Song (NSSound) : 
    def initWithFile_(self, file): 
        self = objc.super(Song, self).initWithContentsOfFile_byReference_(file, True) 
        if self is None: return None
        self.setDelegate_(self)
        return self    
    def sound_didFinishPlaying_(self, _, didFinish):
        print('Success')

class App(tk.Tk):  #representing the rest of the music database app
    def __init__(self):
        super().__init__()         
        file = filedialog.askopenfilename()          
        song = Song.alloc().initWithFile_(file)
        song.play()

if __name__ == "__main__":
    app = App()
    app.mainloop()