【问题标题】:How can one object instance stop sound that other instance is playing?一个对象实例如何停止另一个实例正在播放的声音?
【发布时间】:2013-07-29 04:10:21
【问题描述】:

我对编程有点陌生。我正在使用 Kivy 框架构建一个简单的应用程序。该应用程序有 15 个按钮。当您按下按钮时,它开始播放旋律。如果此时正在播放任何其他按钮,它应该停止,即多个声音(来自不同实例)不应同时播放。

这就是我所做的——我创建了一个处理停止和播放声音的类方法。按下时,按钮实例将声音对象传递给此类方法。但是,在第一次按下时,由于声音对象还不存在,我的应用程序崩溃了。这就是try … except AttributeError 部分的原因。

class Gumb(Button):
    soundfile = StringProperty(None)
    sound = ObjectProperty(None)
    now_playing_object = None

    def on_soundfile(self, instance, value):
        self.sound = SoundLoader.load(value)

    def on_press(self):
        if self.sound:
            self.__class__.play_sound(self.sound)

    @classmethod
    def play_sound(cls, new_sound_object):
        try:
            if cls.now_playing_object.state != 'stop':
                cls.now_playing_object.stop()
        except AttributeError:
            pass
        cls.now_playing_object = new_sound_object
        cls.now_playing_object.play()

这可行,但我不喜欢它,尤其是try … except 部分。必须有更好的方法来做到这一点。 有什么想法吗?

谢谢!

更新 1:

@toto_tico 为我提供了一些绕过try ... except 部分的解决方案。

  1. 使用if 代替异常:

    if cls.now_playing_object != None and cls.now_playing_object.state != 'stop':
        cls.now_playing_object.stop()
    

    虽然,在阅读了有关 python 的 ifs 与异常的效率之后,我不确定我是否应该尝试消除 try ... except。

  2. 一开始就初始化now_playing_object。为此,我使用了:

    now_playing_object = SoundLoader.load(‘non_existant_file.wav’)
    

    这可行,但感觉有点不可靠(奇怪的是,如果文件名缺少扩展名,Kivy 会抱怨,但如果根本没有文件则不会抱怨)。 不过,我想我会用这个。

更新 2:

事实证明,解决方案就在我面前。我只是看不到它。真的不需要@classmethod:

class Gumb(Button):
...
    def play_sound(self):
        if self.__class__.now_playing_object.state is not 'stop':
            self.__class__.now_playing_object.stop()
        self.__class__.now_playing_object = self.sound
        self.__class__.now_playing_object.play()

我已经接受了答案,但真正的答案在 cmets 中。谢谢@toto_tico。

【问题讨论】:

    标签: python oop python-2.7 kivy


    【解决方案1】:

    您可以检查now_playing_object 是否为None。

    代替:

    try:
        if cls.now_playing_object.state != 'stop':
           cls.now_playing_object.stop()
    except AttributeError:
        pass
    ...
    

    你应该可以这样做:

    if cls.now_playing_object != None and cls.now_playing_object.state != 'stop':
        cls.now_playing_object.stop()
    ...
    

    希望对你有帮助。

    【讨论】:

    • 谢谢,我今天学到了一些新东西——依赖条件的顺序是安全的。 this 呢?
    • 不仅仅是订单,更是效率。第二个条件永远不会执行,因为第一个条件 False 足以决定条件为 False,无论下一个语句的值如何。当您期望执行所有指令时,这可能会导致难以捕获的错误。例如,if foo or and_important_method():。在这种情况下,它是一个或,所以如果 foo 是 True,那么 an_important_method() 永远不会被调用,而你是假设的。
    • if 语句效率更高。 Try/catch 需要一些全局管理,并且在 if 不够时使用。最常见的示例是打开文件时。您可以使用if 来验证它是否存在,但在if 和open 之间可能会发生机器中的事情。另一个进程(在另一个核心中或仅仅因为 cpu 中的上下文更改 - 多任务处理)可能会在您的 if 之后删除该文件,并且它不再存在。总之,ifs 更便宜,但有时在现场我们能做的最好的就是try。无论如何,差异是没有意义的。
    • 在这种情况下并不重要——正如你所说,区别是没有意义的——但似乎 Python 的异常实际上比 ifs 便宜。看看here 和here。但是,if 对我来说确实更具可读性。
    • 谢谢,我今天学到了一些新东西。 Python 就像那里的某种宗教——“……请求宽恕比请求许可更容易”。
    【解决方案2】:

    取出if条件:

    如果你不想检查,那么你应该从一开始就初始化变量。我不知道您用于now_playing_object 的类,但我们称它为PlayingObject。

    代替:

    now_playing_object = None
    

    你应该初始化它:

    #maybe needs parameters or simple use now_playing_object = ObjectProperty(None)
    now_playing_object = Sound() 
    

    然后你可以取出我第一个答案(cls.now_playing_object != None)的条件,因为对象总是在初始化。

    要取出@classmethod:

    我发现您使用@classmethod 很有趣。我使用 Python 已经有几年了,老实说,这对我来说是全新的。有几个答案取决于你想要做什么。我假设您很清楚在面向对象编程中class 和instance 之间的区别。

    如果 (1) 您只有一个 Gumb 类的实例或 (2) 您有多个 Gumb 类的实例,但它们都控制相同,则您当前的代码可以工作soundfile、sound 和 now_playing_object。

    现在,如果您希望您的 15 个 Gumb 按钮控制不同的 soundfile、sound 和 now_playing_object,那么您不应该使用 @class 方法,也不要使用声明属性的方式(因为您将它们声明为类属性,并且您需要它们成为实例的一部分)。代码看起来更像这样:

    class Gumb(Button):
        # Constructor. 
        def __init__(self,  **kwargs):
            super(Gump, self).__init__(**kwargs)
            #This became attributes of the 
            self.soundfile = StringProperty(None)\
            self.sound = ObjectProperty(None)
            self.now_playing_object = ObjectProperty(None) 
    
        def on_soundfile(self, instance, value):
            self.sound = SoundLoader.load(value)
    
        def on_press(self):
            if self.sound:
                self.play_sound()
    
        def play_sound(self):
            if self.now_playing_object.state != 'stop':
                self.now_playing_object.stop()
    
            # self.sound was your new_playing_object, since it is part of the instance
            # you don't need to send it as parameter
            self.now_playing_object = self.sound
            self.now_playing_object.play()
    

    这和你想要的更相似吗?

    【讨论】:

    • 关于if:now_playing_object和new_sound_object与self.sound属于同一类。我可以用now_playing_object = SoundLoader.load(‘non_existant_file.wav’) 初始化now_playing_object。这可行,但感觉有点不可靠(奇怪的是,如果文件名缺少扩展名,Kivy 会抱怨,但如果根本没有文件则不会抱怨)。我想没有完美的解决方案:)
    • 关于 @classmethod:我当前的解决方案确实有效,Gumb 实例做控制不同的soundfile 和sound。我的印象是 Kivy 属性(例如 StringProperty 和 ObjectProperty)不是类属性 -- look here。
    • 这很有趣,很抱歉误导了你。你的 now_playing_object 仍然是一个类属性。所有实例共享对当前播放的访问。我认为每个按钮都应该负责(开始和停止)一个轨道。 @classmethod 仍然不是必需的(但仍然可以正确使用),因为您始终可以使用 self.__class__.now_playing_sound 访问类属性,然后使用“正常”方法。我想你从一开始就已经有了完美的解决方案。
    • 您建议的解决方案将停止该特定 Gumb 实例正在播放的声音,但不会停止来自其他实例的声音,即多个声音(每个来自不同实例)可以同时播放。
    • self.__class__.now_playing_object => 这正是我所需要的。谢谢!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多