We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
kivy.uix.Video._on_eos might be called after unoad.
**Software Versions**
* Python: 3.7
* OS: linux
* Kivy: 2.0.0
* Kivy installation method: pip

**Describe the bug**
When using ffpyplayer based video implementation, it's possible that ``eos`` gets set from frame fetching thread after the video has been unload, which results in an ``AttributeError``, since ``self._video`` gets set to ``None`` in ``kivy.uix.Video.unload``.

**Proposed fix**
Add additional check whether ``self._video`` is set in ``_do_eos`` (https://github.com/kivy/kivy/blob/master/kivy/uix/video.py#L260)

```python
    def _on_eos(self, *largs):
        if not self._video or self._video.eos != 'loop':
            self.state = 'stop'
            self.eos = True
```

Any objections? Otherwise i'd create a PR for this.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `kivy/uix/video.py`
Content:
```
1 '''
2 Video
3 =====
4 
5 The :class:`Video` widget is used to display video files and streams.
6 Depending on your Video core provider, platform, and plugins, you will
7 be able to play different formats. For example, the pygame video
8 provider only supports MPEG1 on Linux and OSX. GStreamer is more
9 versatile, and can read many video containers and codecs such as MKV,
10 OGV, AVI, MOV, FLV (if the correct gstreamer plugins are installed). Our
11 :class:`~kivy.core.video.VideoBase` implementation is used under the
12 hood.
13 
14 Video loading is asynchronous - many properties are not available until
15 the video is loaded (when the texture is created)::
16 
17     def on_position_change(instance, value):
18         print('The position in the video is', value)
19 
20     def on_duration_change(instance, value):
21         print('The duration of the video is', value)
22 
23     video = Video(source='PandaSneezes.avi')
24     video.bind(
25         position=on_position_change,
26         duration=on_duration_change
27     )
28 
29 One can define a preview image which gets displayed until the video is
30 started/loaded by passing ``preview`` to the constructor::
31 
32     video = Video(
33         source='PandaSneezes.avi',
34         preview='PandaSneezes_preview.png'
35     )
36 
37 One can display the placeholder image when the video stops by reacting on eos::
38 
39     def on_eos_change(self, inst, val):
40         if val and self.preview:
41             self.set_texture_from_resource(self.preview)
42 
43     video.bind(eos=on_eos_change)
44 '''
45 
46 __all__ = ('Video', )
47 
48 from kivy.clock import Clock
49 from kivy.uix.image import Image
50 from kivy.core.video import Video as CoreVideo
51 from kivy.resources import resource_find
52 from kivy.properties import (BooleanProperty, NumericProperty, ObjectProperty,
53                              OptionProperty, StringProperty)
54 
55 
56 class Video(Image):
57     '''Video class. See module documentation for more information.
58     '''
59 
60     preview = StringProperty(None, allownone=True)
61     '''Filename / source of a preview image displayed before video starts.
62 
63     :attr:`preview` is a :class:`~kivy.properties.StringProperty` and
64     defaults to None.
65 
66     If set, it gets displayed until the video is loaded/started.
67 
68     .. versionadded:: 2.1.0
69     '''
70 
71     state = OptionProperty('stop', options=('play', 'pause', 'stop'))
72     '''String, indicates whether to play, pause, or stop the video::
73 
74         # start playing the video at creation
75         video = Video(source='movie.mkv', state='play')
76 
77         # create the video, and start later
78         video = Video(source='movie.mkv')
79         # and later
80         video.state = 'play'
81 
82     :attr:`state` is an :class:`~kivy.properties.OptionProperty` and defaults
83     to 'stop'.
84     '''
85 
86     play = BooleanProperty(False, deprecated=True)
87     '''
88     .. deprecated:: 1.4.0
89         Use :attr:`state` instead.
90 
91     Boolean, indicates whether the video is playing or not.
92     You can start/stop the video by setting this property::
93 
94         # start playing the video at creation
95         video = Video(source='movie.mkv', play=True)
96 
97         # create the video, and start later
98         video = Video(source='movie.mkv')
99         # and later
100         video.play = True
101 
102     :attr:`play` is a :class:`~kivy.properties.BooleanProperty` and defaults to
103     False.
104 
105     .. deprecated:: 1.4.0
106         Use :attr:`state` instead.
107     '''
108 
109     eos = BooleanProperty(False)
110     '''Boolean, indicates whether the video has finished playing or not
111     (reached the end of the stream).
112 
113     :attr:`eos` is a :class:`~kivy.properties.BooleanProperty` and defaults to
114     False.
115     '''
116 
117     loaded = BooleanProperty(False)
118     '''Boolean, indicates whether the video is loaded and ready for playback
119     or not.
120 
121     .. versionadded:: 1.6.0
122 
123     :attr:`loaded` is a :class:`~kivy.properties.BooleanProperty` and defaults
124     to False.
125     '''
126 
127     position = NumericProperty(-1)
128     '''Position of the video between 0 and :attr:`duration`. The position
129     defaults to -1 and is set to a real position when the video is loaded.
130 
131     :attr:`position` is a :class:`~kivy.properties.NumericProperty` and
132     defaults to -1.
133     '''
134 
135     duration = NumericProperty(-1)
136     '''Duration of the video. The duration defaults to -1, and is set to a real
137     duration when the video is loaded.
138 
139     :attr:`duration` is a :class:`~kivy.properties.NumericProperty` and
140     defaults to -1.
141     '''
142 
143     volume = NumericProperty(1.)
144     '''Volume of the video, in the range 0-1. 1 means full volume, 0
145     means mute.
146 
147     :attr:`volume` is a :class:`~kivy.properties.NumericProperty` and defaults
148     to 1.
149     '''
150 
151     options = ObjectProperty({})
152     '''Options to pass at Video core object creation.
153 
154     .. versionadded:: 1.0.4
155 
156     :attr:`options` is an :class:`kivy.properties.ObjectProperty` and defaults
157     to {}.
158     '''
159 
160     _video_load_event = None
161 
162     def __init__(self, **kwargs):
163         self._video = None
164         super(Video, self).__init__(**kwargs)
165         self.fbind('source', self._trigger_video_load)
166 
167         if "eos" in kwargs:
168             self.options["eos"] = kwargs["eos"]
169         if self.source:
170             self._trigger_video_load()
171 
172     def texture_update(self, *largs):
173         if self.preview:
174             self.set_texture_from_resource(self.preview)
175         else:
176             self.set_texture_from_resource(self.source)
177 
178     def seek(self, percent, precise=True):
179         '''Change the position to a percentage (strictly, a proportion)
180            of duration.
181 
182         :Parameters:
183             `percent`: float or int
184                 Position to seek as a proportion of the total duration,
185                 must be between 0-1.
186             `precise`: bool, defaults to True
187                 Precise seeking is slower, but seeks to exact requested
188                 percent.
189 
190         .. warning::
191             Calling seek() before the video is loaded has no effect.
192 
193         .. versionadded:: 1.2.0
194 
195         .. versionchanged:: 1.10.1
196             The `precise` keyword argument has been added.
197         '''
198         if self._video is None:
199             raise Exception('Video not loaded.')
200         self._video.seek(percent, precise=precise)
201 
202     def _trigger_video_load(self, *largs):
203         ev = self._video_load_event
204         if ev is None:
205             ev = self._video_load_event = Clock.schedule_once(
206                 self._do_video_load, -1)
207         ev()
208 
209     def _do_video_load(self, *largs):
210         if CoreVideo is None:
211             return
212         self.unload()
213         if not self.source:
214             self._video = None
215             self.texture = None
216         else:
217             filename = self.source
218             # Check if filename is not url
219             if '://' not in filename:
220                 filename = resource_find(filename)
221             self._video = CoreVideo(filename=filename, **self.options)
222             self._video.volume = self.volume
223             self._video.bind(on_load=self._on_load,
224                              on_frame=self._on_video_frame,
225                              on_eos=self._on_eos)
226             if self.state == 'play' or self.play:
227                 self._video.play()
228             self.duration = 1.
229             self.position = 0.
230 
231     def on_play(self, instance, value):
232         value = 'play' if value else 'stop'
233         return self.on_state(instance, value)
234 
235     def on_state(self, instance, value):
236         if not self._video:
237             return
238         if value == 'play':
239             if self.eos:
240                 self._video.stop()
241                 self._video.position = 0.
242             self.eos = False
243             self._video.play()
244         elif value == 'pause':
245             self._video.pause()
246         else:
247             self._video.stop()
248             self._video.position = 0
249 
250     def _on_video_frame(self, *largs):
251         video = self._video
252         if not video:
253             return
254         self.duration = video.duration
255         self.position = video.position
256         self.texture = video.texture
257         self.canvas.ask_update()
258 
259     def _on_eos(self, *largs):
260         if self._video.eos != 'loop':
261             self.state = 'stop'
262             self.eos = True
263 
264     def _on_load(self, *largs):
265         self.loaded = True
266         self._on_video_frame(largs)
267 
268     def on_volume(self, instance, value):
269         if self._video:
270             self._video.volume = value
271 
272     def unload(self):
273         '''Unload the video. The playback will be stopped.
274 
275         .. versionadded:: 1.8.0
276         '''
277         if self._video:
278             self._video.stop()
279             self._video.unload()
280             self._video = None
281         self.loaded = False
282 
283 
284 if __name__ == '__main__':
285     from kivy.app import App
286     import sys
287 
288     if len(sys.argv) != 2:
289         print("usage: %s file" % sys.argv[0])
290         sys.exit(1)
291 
292     class VideoApp(App):
293         def build(self):
294             self.v = Video(source=sys.argv[1], state='play')
295             self.v.bind(state=self.replay)
296             return self.v
297 
298         def replay(self, *args):
299             if self.v.state == 'stop':
300                 self.v.state = 'play'
301 
302     VideoApp().run()
303
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/kivy/uix/video.py b/kivy/uix/video.py
--- a/kivy/uix/video.py
+++ b/kivy/uix/video.py
@@ -257,7 +257,7 @@
         self.canvas.ask_update()
 
     def _on_eos(self, *largs):
-        if self._video.eos != 'loop':
+        if not self._video or self._video.eos != 'loop':
             self.state = 'stop'
             self.eos = True

{"golden_diff": "diff --git a/kivy/uix/video.py b/kivy/uix/video.py\n--- a/kivy/uix/video.py\n+++ b/kivy/uix/video.py\n@@ -257,7 +257,7 @@\n         self.canvas.ask_update()\n \n     def _on_eos(self, *largs):\n-        if self._video.eos != 'loop':\n+        if not self._video or self._video.eos != 'loop':\n             self.state = 'stop'\n             self.eos = True\n", "issue": "kivy.uix.Video._on_eos might be called after unoad.\n**Software Versions**\r\n* Python: 3.7\r\n* OS: linux\r\n* Kivy: 2.0.0\r\n* Kivy installation method: pip\r\n\r\n**Describe the bug**\r\nWhen using ffpyplayer based video implementation, it's possible that ``eos`` gets set from frame fetching thread after the video has been unload, which results in an ``AttributeError``, since ``self._video`` gets set to ``None`` in ``kivy.uix.Video.unload``.\r\n\r\n**Proposed fix**\r\nAdd additional check whether ``self._video`` is set in ``_do_eos`` (https://github.com/kivy/kivy/blob/master/kivy/uix/video.py#L260)\r\n\r\n```python\r\n    def _on_eos(self, *largs):\r\n        if not self._video or self._video.eos != 'loop':\r\n            self.state = 'stop'\r\n            self.eos = True\r\n```\r\n\r\nAny objections? Otherwise i'd create a PR for this.\n", "before_files": [{"content": "'''\nVideo\n=====\n\nThe :class:`Video` widget is used to display video files and streams.\nDepending on your Video core provider, platform, and plugins, you will\nbe able to play different formats. For example, the pygame video\nprovider only supports MPEG1 on Linux and OSX. GStreamer is more\nversatile, and can read many video containers and codecs such as MKV,\nOGV, AVI, MOV, FLV (if the correct gstreamer plugins are installed). Our\n:class:`~kivy.core.video.VideoBase` implementation is used under the\nhood.\n\nVideo loading is asynchronous - many properties are not available until\nthe video is loaded (when the texture is created)::\n\n    def on_position_change(instance, value):\n        print('The position in the video is', value)\n\n    def on_duration_change(instance, value):\n        print('The duration of the video is', value)\n\n    video = Video(source='PandaSneezes.avi')\n    video.bind(\n        position=on_position_change,\n        duration=on_duration_change\n    )\n\nOne can define a preview image which gets displayed until the video is\nstarted/loaded by passing ``preview`` to the constructor::\n\n    video = Video(\n        source='PandaSneezes.avi',\n        preview='PandaSneezes_preview.png'\n    )\n\nOne can display the placeholder image when the video stops by reacting on eos::\n\n    def on_eos_change(self, inst, val):\n        if val and self.preview:\n            self.set_texture_from_resource(self.preview)\n\n    video.bind(eos=on_eos_change)\n'''\n\n__all__ = ('Video', )\n\nfrom kivy.clock import Clock\nfrom kivy.uix.image import Image\nfrom kivy.core.video import Video as CoreVideo\nfrom kivy.resources import resource_find\nfrom kivy.properties import (BooleanProperty, NumericProperty, ObjectProperty,\n                             OptionProperty, StringProperty)\n\n\nclass Video(Image):\n    '''Video class. See module documentation for more information.\n    '''\n\n    preview = StringProperty(None, allownone=True)\n    '''Filename / source of a preview image displayed before video starts.\n\n    :attr:`preview` is a :class:`~kivy.properties.StringProperty` and\n    defaults to None.\n\n    If set, it gets displayed until the video is loaded/started.\n\n    .. versionadded:: 2.1.0\n    '''\n\n    state = OptionProperty('stop', options=('play', 'pause', 'stop'))\n    '''String, indicates whether to play, pause, or stop the video::\n\n        # start playing the video at creation\n        video = Video(source='movie.mkv', state='play')\n\n        # create the video, and start later\n        video = Video(source='movie.mkv')\n        # and later\n        video.state = 'play'\n\n    :attr:`state` is an :class:`~kivy.properties.OptionProperty` and defaults\n    to 'stop'.\n    '''\n\n    play = BooleanProperty(False, deprecated=True)\n    '''\n    .. deprecated:: 1.4.0\n        Use :attr:`state` instead.\n\n    Boolean, indicates whether the video is playing or not.\n    You can start/stop the video by setting this property::\n\n        # start playing the video at creation\n        video = Video(source='movie.mkv', play=True)\n\n        # create the video, and start later\n        video = Video(source='movie.mkv')\n        # and later\n        video.play = True\n\n    :attr:`play` is a :class:`~kivy.properties.BooleanProperty` and defaults to\n    False.\n\n    .. deprecated:: 1.4.0\n        Use :attr:`state` instead.\n    '''\n\n    eos = BooleanProperty(False)\n    '''Boolean, indicates whether the video has finished playing or not\n    (reached the end of the stream).\n\n    :attr:`eos` is a :class:`~kivy.properties.BooleanProperty` and defaults to\n    False.\n    '''\n\n    loaded = BooleanProperty(False)\n    '''Boolean, indicates whether the video is loaded and ready for playback\n    or not.\n\n    .. versionadded:: 1.6.0\n\n    :attr:`loaded` is a :class:`~kivy.properties.BooleanProperty` and defaults\n    to False.\n    '''\n\n    position = NumericProperty(-1)\n    '''Position of the video between 0 and :attr:`duration`. The position\n    defaults to -1 and is set to a real position when the video is loaded.\n\n    :attr:`position` is a :class:`~kivy.properties.NumericProperty` and\n    defaults to -1.\n    '''\n\n    duration = NumericProperty(-1)\n    '''Duration of the video. The duration defaults to -1, and is set to a real\n    duration when the video is loaded.\n\n    :attr:`duration` is a :class:`~kivy.properties.NumericProperty` and\n    defaults to -1.\n    '''\n\n    volume = NumericProperty(1.)\n    '''Volume of the video, in the range 0-1. 1 means full volume, 0\n    means mute.\n\n    :attr:`volume` is a :class:`~kivy.properties.NumericProperty` and defaults\n    to 1.\n    '''\n\n    options = ObjectProperty({})\n    '''Options to pass at Video core object creation.\n\n    .. versionadded:: 1.0.4\n\n    :attr:`options` is an :class:`kivy.properties.ObjectProperty` and defaults\n    to {}.\n    '''\n\n    _video_load_event = None\n\n    def __init__(self, **kwargs):\n        self._video = None\n        super(Video, self).__init__(**kwargs)\n        self.fbind('source', self._trigger_video_load)\n\n        if \"eos\" in kwargs:\n            self.options[\"eos\"] = kwargs[\"eos\"]\n        if self.source:\n            self._trigger_video_load()\n\n    def texture_update(self, *largs):\n        if self.preview:\n            self.set_texture_from_resource(self.preview)\n        else:\n            self.set_texture_from_resource(self.source)\n\n    def seek(self, percent, precise=True):\n        '''Change the position to a percentage (strictly, a proportion)\n           of duration.\n\n        :Parameters:\n            `percent`: float or int\n                Position to seek as a proportion of the total duration,\n                must be between 0-1.\n            `precise`: bool, defaults to True\n                Precise seeking is slower, but seeks to exact requested\n                percent.\n\n        .. warning::\n            Calling seek() before the video is loaded has no effect.\n\n        .. versionadded:: 1.2.0\n\n        .. versionchanged:: 1.10.1\n            The `precise` keyword argument has been added.\n        '''\n        if self._video is None:\n            raise Exception('Video not loaded.')\n        self._video.seek(percent, precise=precise)\n\n    def _trigger_video_load(self, *largs):\n        ev = self._video_load_event\n        if ev is None:\n            ev = self._video_load_event = Clock.schedule_once(\n                self._do_video_load, -1)\n        ev()\n\n    def _do_video_load(self, *largs):\n        if CoreVideo is None:\n            return\n        self.unload()\n        if not self.source:\n            self._video = None\n            self.texture = None\n        else:\n            filename = self.source\n            # Check if filename is not url\n            if '://' not in filename:\n                filename = resource_find(filename)\n            self._video = CoreVideo(filename=filename, **self.options)\n            self._video.volume = self.volume\n            self._video.bind(on_load=self._on_load,\n                             on_frame=self._on_video_frame,\n                             on_eos=self._on_eos)\n            if self.state == 'play' or self.play:\n                self._video.play()\n            self.duration = 1.\n            self.position = 0.\n\n    def on_play(self, instance, value):\n        value = 'play' if value else 'stop'\n        return self.on_state(instance, value)\n\n    def on_state(self, instance, value):\n        if not self._video:\n            return\n        if value == 'play':\n            if self.eos:\n                self._video.stop()\n                self._video.position = 0.\n            self.eos = False\n            self._video.play()\n        elif value == 'pause':\n            self._video.pause()\n        else:\n            self._video.stop()\n            self._video.position = 0\n\n    def _on_video_frame(self, *largs):\n        video = self._video\n        if not video:\n            return\n        self.duration = video.duration\n        self.position = video.position\n        self.texture = video.texture\n        self.canvas.ask_update()\n\n    def _on_eos(self, *largs):\n        if self._video.eos != 'loop':\n            self.state = 'stop'\n            self.eos = True\n\n    def _on_load(self, *largs):\n        self.loaded = True\n        self._on_video_frame(largs)\n\n    def on_volume(self, instance, value):\n        if self._video:\n            self._video.volume = value\n\n    def unload(self):\n        '''Unload the video. The playback will be stopped.\n\n        .. versionadded:: 1.8.0\n        '''\n        if self._video:\n            self._video.stop()\n            self._video.unload()\n            self._video = None\n        self.loaded = False\n\n\nif __name__ == '__main__':\n    from kivy.app import App\n    import sys\n\n    if len(sys.argv) != 2:\n        print(\"usage: %s file\" % sys.argv[0])\n        sys.exit(1)\n\n    class VideoApp(App):\n        def build(self):\n            self.v = Video(source=sys.argv[1], state='play')\n            self.v.bind(state=self.replay)\n            return self.v\n\n        def replay(self, *args):\n            if self.v.state == 'stop':\n                self.v.state = 'play'\n\n    VideoApp().run()\n", "path": "kivy/uix/video.py"}], "after_files": [{"content": "'''\nVideo\n=====\n\nThe :class:`Video` widget is used to display video files and streams.\nDepending on your Video core provider, platform, and plugins, you will\nbe able to play different formats. For example, the pygame video\nprovider only supports MPEG1 on Linux and OSX. GStreamer is more\nversatile, and can read many video containers and codecs such as MKV,\nOGV, AVI, MOV, FLV (if the correct gstreamer plugins are installed). Our\n:class:`~kivy.core.video.VideoBase` implementation is used under the\nhood.\n\nVideo loading is asynchronous - many properties are not available until\nthe video is loaded (when the texture is created)::\n\n    def on_position_change(instance, value):\n        print('The position in the video is', value)\n\n    def on_duration_change(instance, value):\n        print('The duration of the video is', value)\n\n    video = Video(source='PandaSneezes.avi')\n    video.bind(\n        position=on_position_change,\n        duration=on_duration_change\n    )\n\nOne can define a preview image which gets displayed until the video is\nstarted/loaded by passing ``preview`` to the constructor::\n\n    video = Video(\n        source='PandaSneezes.avi',\n        preview='PandaSneezes_preview.png'\n    )\n\nOne can display the placeholder image when the video stops by reacting on eos::\n\n    def on_eos_change(self, inst, val):\n        if val and self.preview:\n            self.set_texture_from_resource(self.preview)\n\n    video.bind(eos=on_eos_change)\n'''\n\n__all__ = ('Video', )\n\nfrom kivy.clock import Clock\nfrom kivy.uix.image import Image\nfrom kivy.core.video import Video as CoreVideo\nfrom kivy.resources import resource_find\nfrom kivy.properties import (BooleanProperty, NumericProperty, ObjectProperty,\n                             OptionProperty, StringProperty)\n\n\nclass Video(Image):\n    '''Video class. See module documentation for more information.\n    '''\n\n    preview = StringProperty(None, allownone=True)\n    '''Filename / source of a preview image displayed before video starts.\n\n    :attr:`preview` is a :class:`~kivy.properties.StringProperty` and\n    defaults to None.\n\n    If set, it gets displayed until the video is loaded/started.\n\n    .. versionadded:: 2.1.0\n    '''\n\n    state = OptionProperty('stop', options=('play', 'pause', 'stop'))\n    '''String, indicates whether to play, pause, or stop the video::\n\n        # start playing the video at creation\n        video = Video(source='movie.mkv', state='play')\n\n        # create the video, and start later\n        video = Video(source='movie.mkv')\n        # and later\n        video.state = 'play'\n\n    :attr:`state` is an :class:`~kivy.properties.OptionProperty` and defaults\n    to 'stop'.\n    '''\n\n    play = BooleanProperty(False, deprecated=True)\n    '''\n    .. deprecated:: 1.4.0\n        Use :attr:`state` instead.\n\n    Boolean, indicates whether the video is playing or not.\n    You can start/stop the video by setting this property::\n\n        # start playing the video at creation\n        video = Video(source='movie.mkv', play=True)\n\n        # create the video, and start later\n        video = Video(source='movie.mkv')\n        # and later\n        video.play = True\n\n    :attr:`play` is a :class:`~kivy.properties.BooleanProperty` and defaults to\n    False.\n\n    .. deprecated:: 1.4.0\n        Use :attr:`state` instead.\n    '''\n\n    eos = BooleanProperty(False)\n    '''Boolean, indicates whether the video has finished playing or not\n    (reached the end of the stream).\n\n    :attr:`eos` is a :class:`~kivy.properties.BooleanProperty` and defaults to\n    False.\n    '''\n\n    loaded = BooleanProperty(False)\n    '''Boolean, indicates whether the video is loaded and ready for playback\n    or not.\n\n    .. versionadded:: 1.6.0\n\n    :attr:`loaded` is a :class:`~kivy.properties.BooleanProperty` and defaults\n    to False.\n    '''\n\n    position = NumericProperty(-1)\n    '''Position of the video between 0 and :attr:`duration`. The position\n    defaults to -1 and is set to a real position when the video is loaded.\n\n    :attr:`position` is a :class:`~kivy.properties.NumericProperty` and\n    defaults to -1.\n    '''\n\n    duration = NumericProperty(-1)\n    '''Duration of the video. The duration defaults to -1, and is set to a real\n    duration when the video is loaded.\n\n    :attr:`duration` is a :class:`~kivy.properties.NumericProperty` and\n    defaults to -1.\n    '''\n\n    volume = NumericProperty(1.)\n    '''Volume of the video, in the range 0-1. 1 means full volume, 0\n    means mute.\n\n    :attr:`volume` is a :class:`~kivy.properties.NumericProperty` and defaults\n    to 1.\n    '''\n\n    options = ObjectProperty({})\n    '''Options to pass at Video core object creation.\n\n    .. versionadded:: 1.0.4\n\n    :attr:`options` is an :class:`kivy.properties.ObjectProperty` and defaults\n    to {}.\n    '''\n\n    _video_load_event = None\n\n    def __init__(self, **kwargs):\n        self._video = None\n        super(Video, self).__init__(**kwargs)\n        self.fbind('source', self._trigger_video_load)\n\n        if \"eos\" in kwargs:\n            self.options[\"eos\"] = kwargs[\"eos\"]\n        if self.source:\n            self._trigger_video_load()\n\n    def texture_update(self, *largs):\n        if self.preview:\n            self.set_texture_from_resource(self.preview)\n        else:\n            self.set_texture_from_resource(self.source)\n\n    def seek(self, percent, precise=True):\n        '''Change the position to a percentage (strictly, a proportion)\n           of duration.\n\n        :Parameters:\n            `percent`: float or int\n                Position to seek as a proportion of the total duration,\n                must be between 0-1.\n            `precise`: bool, defaults to True\n                Precise seeking is slower, but seeks to exact requested\n                percent.\n\n        .. warning::\n            Calling seek() before the video is loaded has no effect.\n\n        .. versionadded:: 1.2.0\n\n        .. versionchanged:: 1.10.1\n            The `precise` keyword argument has been added.\n        '''\n        if self._video is None:\n            raise Exception('Video not loaded.')\n        self._video.seek(percent, precise=precise)\n\n    def _trigger_video_load(self, *largs):\n        ev = self._video_load_event\n        if ev is None:\n            ev = self._video_load_event = Clock.schedule_once(\n                self._do_video_load, -1)\n        ev()\n\n    def _do_video_load(self, *largs):\n        if CoreVideo is None:\n            return\n        self.unload()\n        if not self.source:\n            self._video = None\n            self.texture = None\n        else:\n            filename = self.source\n            # Check if filename is not url\n            if '://' not in filename:\n                filename = resource_find(filename)\n            self._video = CoreVideo(filename=filename, **self.options)\n            self._video.volume = self.volume\n            self._video.bind(on_load=self._on_load,\n                             on_frame=self._on_video_frame,\n                             on_eos=self._on_eos)\n            if self.state == 'play' or self.play:\n                self._video.play()\n            self.duration = 1.\n            self.position = 0.\n\n    def on_play(self, instance, value):\n        value = 'play' if value else 'stop'\n        return self.on_state(instance, value)\n\n    def on_state(self, instance, value):\n        if not self._video:\n            return\n        if value == 'play':\n            if self.eos:\n                self._video.stop()\n                self._video.position = 0.\n            self.eos = False\n            self._video.play()\n        elif value == 'pause':\n            self._video.pause()\n        else:\n            self._video.stop()\n            self._video.position = 0\n\n    def _on_video_frame(self, *largs):\n        video = self._video\n        if not video:\n            return\n        self.duration = video.duration\n        self.position = video.position\n        self.texture = video.texture\n        self.canvas.ask_update()\n\n    def _on_eos(self, *largs):\n        if not self._video or self._video.eos != 'loop':\n            self.state = 'stop'\n            self.eos = True\n\n    def _on_load(self, *largs):\n        self.loaded = True\n        self._on_video_frame(largs)\n\n    def on_volume(self, instance, value):\n        if self._video:\n            self._video.volume = value\n\n    def unload(self):\n        '''Unload the video. The playback will be stopped.\n\n        .. versionadded:: 1.8.0\n        '''\n        if self._video:\n            self._video.stop()\n            self._video.unload()\n            self._video = None\n        self.loaded = False\n\n\nif __name__ == '__main__':\n    from kivy.app import App\n    import sys\n\n    if len(sys.argv) != 2:\n        print(\"usage: %s file\" % sys.argv[0])\n        sys.exit(1)\n\n    class VideoApp(App):\n        def build(self):\n            self.v = Video(source=sys.argv[1], state='play')\n            self.v.bind(state=self.replay)\n            return self.v\n\n        def replay(self, *args):\n            if self.v.state == 'stop':\n                self.v.state = 'play'\n\n    VideoApp().run()\n", "path": "kivy/uix/video.py"}]}