Repository: psychopy/psychopy

Issue:
MovieStim3 do not stop playing the audio when stop() is called
## Problem

MovieStim3 do not stop playing the audio stream though stop is called.
I'm not sure if the problem was  caused by large video files (the used file was 215 MB) or the music followed.

## Example code to reproduce issue

- [Download the used files (**Large file**: 236MB)](https://tinyurl.com/yaqqhksc)

```python
from psychopy import visual, core, event, prefs
prefs.general['audioLib'] = ['sounddevice']
from psychopy import sound

win = visual.Window(
    size=[1280, 1024],
    allowGUI=False)

attention_grabber = visual.MovieStim3(
    win,
    "infant/elmo's ducks.mp4",
    size=[640, 480],
    units='pix',
    autoLog=False)

music = sound.Sound('infant/Rainy_Day_Games.wav', sampleRate=44100, stereo=True, loops=-1)

att_timer = core.CountdownTimer(attention_grabber.duration)
playing = True
attention_grabber.play()
att_timer.reset()
while att_timer.getTime() > 0 and playing:
    attention_grabber.draw()
    for key in event.getKeys():
        if key == 'escape' or key == 'space':
            playing = False
            break
    win.flip()
attention_grabber.stop()

music_timer = core.CountdownTimer(music.duration)
playing = True
music.play()
music_timer.reset()
while music_timer.getTime() > 0 and playing:
    for key in event.getKeys():
        if key == 'escape' or key == 'space':
            playing = False
            break
    win.flip()
music.stop()

win.close()
core.quit()
```

## Versions
- Linux: 4.17.13-041713-generic/ ubuntu 18.04.1
- PsychoPy 1.90.3 (used pip to install from the source code)
- Python: 3.5.6
- Full list of packages
```
asn1crypto==0.24.0
astroid==2.0.2
certifi==2018.4.16
cffi==1.11.5
chardet==3.0.4
configobj==5.0.6
cryptography==2.3
cycler==0.10.0
decorator==4.3.0
et-xmlfile==1.0.1
future==0.16.0
gevent==1.3.5
greenlet==0.4.14
idna==2.7
imageio==2.3.0
isort==4.3.4
jdcal==1.4
json-tricks==3.12.2
kiwisolver==1.0.1
lazy-object-proxy==1.3.1
matplotlib==2.2.2
mccabe==0.6.1
moviepy==0.2.3.5
msgpack-python==0.5.6
numexpr==2.6.6
numpy==1.15.0
opencv-python==3.4.2.17
openpyxl==2.5.5
pandas==0.23.4
Pillow==5.2.0
psutil==5.4.6
PsychoPy==1.90.3
psychopy-tobii-controller==1.1.0
pycparser==2.18
pygame==1.9.4
pyglet==1.3.2
pylint==2.1.0
PyOpenGL==3.1.0
pyOpenSSL==18.0.0
pyosf==1.0.5
pyparallel==0.2.2
pyparsing==2.2.0
Pypubsub==4.0.0
PyQt5==5.11.2
PyQt5-sip==4.19.12
pyserial==3.4
python-bidi==0.4.0
python-dateutil==2.7.3
pytz==2018.5
PyYAML==3.13
pyzmq==17.1.0
requests==2.19.1
scipy==1.1.0
six==1.11.0
sounddevice==0.3.11
SoundFile==0.10.2
tables==3.4.4
tobii-research==1.5.0
tqdm==4.24.0
typed-ast==1.1.0
typing==3.6.4
urllib3==1.23
wrapt==1.10.11
wxPython==4.0.3
xlrd==1.1.0
yapf==0.22.0
zmq==0.0.0
```

## Suggested fix
Directly call `stop` in `_unload` seemed to fix the issue. I also noticed that #1917 caused problem when `self._mov` is `None` though I'm not sure why it would be `None`...
```python
    def _unload(self):
        try:
            # remove textures from graphics card to prevent crash
            self.clearTextures()
        except Exception:
            pass
        if self._mov is not None:
            self._mov.close()
        self._mov = None
        self._numpyFrame = None
        if self._audioStream is not None:
            self._audioStream.stop()
        self._audioStream = None
        self.status = FINISHED
```


Discussion:
Do you want to submit a pull request with this (with prefix BF: on the commit message since it's an existing bug)? That way you'll be credited in the list of contributors for all time! 😄 
(the close was an accident - missed the button with the mouse!)

Resolving patch:
diff --git a/psychopy/visual/movie3.py b/psychopy/visual/movie3.py
--- a/psychopy/visual/movie3.py
+++ b/psychopy/visual/movie3.py
@@ -472,9 +472,12 @@ def _unload(self):
             self.clearTextures()
         except Exception:
             pass
-        self._mov.close()
+        if self._mov is not None:
+            self._mov.close()
         self._mov = None
         self._numpyFrame = None
+        if self._audioStream is not None:
+            self._audioStream.stop()
         self._audioStream = None
         self.status = FINISHED
 


Test patch: