Home > Blockchain >  Pygame mixer causes tkinter to freeze
Pygame mixer causes tkinter to freeze

Time:10-09

to-speech module to say a list and the audio module I am using is mixer form pygame. When i start playing the audio the tkinter program can not be interacted with and if I move the window the audio stops. I think some sort of threading may fix this but I am not sure how that works.

Problem
Tkinter window freezes.

Goal
To be able to interact with the program when audio is playing.

Code

import tkinter as tk
from gtts import gTTS
from io import BytesIO
import pygame

window = tk.Tk()
window.geometry("800x600")
window.resizable(0, 0)

def speak(text,language="en",accent="com"):
    mp3_fp = BytesIO()
    phrase = gTTS(text=text,lang=language,tld=accent)
    phrase.write_to_fp(mp3_fp)
    pygame.init()
    pygame.mixer.init()
    pygame.mixer.music.load(mp3_fp,"mp3")
    pygame.mixer.music.play()
    while pygame.mixer.music.get_busy():
        pygame.time.delay(10)
        pygame.event.poll()

def play():
    data = list([boy,girl,pink,blue])
    for i in data:
        speak(i)
window.mainloop()

Code Explanation
The play()passes each value in the dictionary E.g. Boy, Girl to speak() separately where they are played one by one consecutively In the speak() the audio from the text-to-speech module (gTTS) gets passed to pygame.mixer where it is played when the last word has been said.

CodePudding user response:

First of all you need to post the minimum code necessary to demonstrate your problem. You haven't done that. Fortunately, this is a well-known "problem" and easy to answer without importing four libraries and building an application around what you've provided in order to answer it.

Secondly -- in the code that you have provided, data = list[boy,girl,pink,blue] isn't even proper syntax. It should be data = list(["boy", "girl", "pink", "blue"]). You have to post running code to get the best answers.

Lecture over.

The issue is that conventional unmodified Python runs in a single thread. If you want to know why that is then I invite you to research the GIL (Global Interpreter Lock) for more background.

There's only one thread, and when PyGame is doing something then the thread is busy and TkInter stops responding to input, and vice versa -- when TkInter is in the middle of something you'll find that PyGame stops responding.

You can demonstrate this phenomenon with this:

import tkinter as tk
import time

def delay():
    time.sleep(10)

def main():
    root = tk.Tk()
    tk.Button(root, text="Test Me", command=delay).pack(expand=True, fill=tk.BOTH)
    
    root.mainloop()

if __name__ == "__main__":
    main()

When you run this you'll see that the button is depressed, the button stays depressed while the application goes to sleep, and the button doesn't go back to its unclicked status until after it wakes up.

The only way I know of to get around your particular problem is by running TkInter and/or PyGame on separate threads.

You are going to have to read up on Python's Threading() module. You might start here. I've browsed it and it seems to be pretty complete.

Just to demonstrate the difference:

import tkinter as tk
import time
import threading

def delay():
    print("Delay started...")
    time.sleep(10)
    print("... and finished.")

def dispatchDelayToThread():
    t = threading.Thread(target=delay)
    t.start()

def main():
    root = tk.Tk()
    tk.Button(root, text="Test Me", command=dispatchDelayToThread).pack(expand=True, fill=tk.BOTH)
    
    root.mainloop()

if __name__ == "__main__":
    main()

I didn't even really change the code any! I added a function to dispatch the code I'd already written then changed the button to call the dispatcher instead of the code. Very easy to implement.

Run this and you'll see that the button returns to ready right away. If you run this from the command line you'll see that it prints a line when you enter the thread, and another line when the thread completes. And an even cooler thing is that if you click the button three times in a row you'll get three "starting" message, followed by three "finished" messages shortly afterwards.

To demonstrate this threading using your own code:

import pygame
import tkinter as tk
import io
import gtts
import threading

def speak(text,language="en",accent="com"):
    mp3_fp = io.BytesIO()
    phrase = gtts.gTTS(text=text,lang=language,tld=accent)
    phrase.write_to_fp(mp3_fp)
    pygame.init()
    pygame.mixer.init()
    pygame.mixer.music.load(mp3_fp,"mp3")
    pygame.mixer.music.play()
    while pygame.mixer.music.get_busy():
        pygame.time.delay(10)
        pygame.event.poll()

def dispatchPlay():
    t = threading.Thread(target=play)
    t.start()

def play():
    data = list(["boy", "girl", "pink", "blue"])
    for i in data:
        speak(i)

def main():
    root = tk.Tk()
    root.geometry('300x200')
    tk.Button(root, text="This is a clicky button", command=dispatchPlay).pack(expand=True, fill=tk.BOTH)

    root.mainloop()

if __name__ == "__main__":
    main()

Generally you would have your user interface on one thread, frame updates on another thread if it's a game, sound stuff on yet another thread, networking connection on a thread, etc., with all of them tied together through some kind of messaging system.

Note well, however, that in conventional unmodified Python there is only ever one thread running at any one time! You can have a hundred threads spawned, but only one of them will run at a time. Python sucks at multithreading computationally-intensive tasks, but shines in threading out I/O stuff that you usually just spend your time waiting on.

CodePudding user response:

problem it may be at pygame.time.delay(10)

https://www.pygame.org/docs/ref/time.html#pygame.time.delay

  • Related