We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Properly clause websocket connection on video/webinar deletion
## Bug Report

**Problematic Behavior**

When a video/webinar is deleted, a websocket connection is still open. The dashboard is unmounted by react so it tries to close it but an error is thrown because on closing the connection, the weboscket tries to fetch the attached live session in database but this one does not exists anymore.

**Expected behavior/code**

When a video/webinar is deleted, the websocket should be properly closed.

**Steps to Reproduce**
1. Create a webinar
2. Delete it


**Additional context/Screenshots**

https://gip-fun-mooc.sentry.io/share/issue/668071c3736a446a890af2b99df1c6c6/


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/backend/marsha/websocket/consumers/video.py`
Content:
```
1 """Video consumer module"""
2 from urllib.parse import parse_qs
3 
4 from django.db.models import Q
5 
6 from channels.db import database_sync_to_async
7 from channels.generic.websocket import AsyncJsonWebsocketConsumer
8 
9 from marsha.core.models import (
10     ADMINISTRATOR,
11     INSTRUCTOR,
12     SharedLiveMedia,
13     Thumbnail,
14     TimedTextTrack,
15     Video,
16 )
17 from marsha.core.permissions import IsTokenAdmin, IsTokenInstructor
18 from marsha.core.services import live_session as LiveSessionServices
19 from marsha.core.simple_jwt.tokens import ResourceAccessToken, UserAccessToken
20 from marsha.websocket import defaults
21 
22 
23 class VideoConsumer(AsyncJsonWebsocketConsumer):
24     """Video consumer."""
25 
26     room_group_name = None
27     is_connected = False
28 
29     def __get_video_id(self):
30         return self.scope["url_route"]["kwargs"]["video_id"]
31 
32     async def _check_permissions(self):
33         """
34         Check if the user has the required permissions.
35 
36         Raises:
37             ConnectionRefusedError: if the user does not have the required permissions.
38         """
39         token = self.scope["token"]
40         if token is None:
41             raise ConnectionRefusedError()
42 
43         # Check permissions, MUST be the same as in the `retrieve` method
44         # of the Video API view set.
45 
46         if isinstance(token, ResourceAccessToken):
47             # With LTI: anyone with a valid token for the video can access
48             if token.payload.get("resource_id") != self.__get_video_id():
49                 raise ConnectionRefusedError()
50 
51         elif isinstance(token, UserAccessToken):
52             # With standalone site, only playlist admin or organization admin can access
53             if not await self._user_has_playlist_or_organization_admin_role(
54                 token.payload.get("user_id")
55             ):
56                 raise ConnectionRefusedError()
57 
58         else:
59             raise RuntimeError("This should not happen")
60 
61     async def connect(self):
62         """
63         Manage connection to this consumer.
64         During handshake it is not possible to close the websocket with a specific
65         code. To do that we must accept first the connection and then close it with the code we
66         want
67         """
68         try:
69             await self._check_permissions()
70             await self._check_video_exists()
71             if not await self._is_admin():
72                 live_session = await self.retrieve_live_session()
73                 await self.update_live_session_with_channel_name(
74                     live_session=live_session
75                 )
76         except ConnectionRefusedError:
77             await self.accept()
78             return await self.close(code=4003)
79 
80         self.room_group_name = await self._get_room_name()
81         # Join room group
82         await self.channel_layer.group_add(self.room_group_name, self.channel_name)
83 
84         await self.accept()
85         self.is_connected = True
86 
87     async def _check_video_exists(self):
88         """Close the room if the video does not exists."""
89         if not await self._video_exists():
90             raise ConnectionRefusedError()
91 
92     @database_sync_to_async
93     def _video_exists(self):
94         """Return if a video exists in database or not."""
95         return Video.objects.filter(pk=self.__get_video_id()).exists()
96 
97     @database_sync_to_async
98     def _user_has_playlist_or_organization_admin_role(self, user_id):
99         """Return if the user belongs to the video playlist admin or organization admin."""
100         return Video.objects.filter(
101             Q(pk=self.__get_video_id())
102             & (
103                 Q(
104                     playlist__user_accesses__user_id=user_id,
105                     playlist__user_accesses__role__in=[ADMINISTRATOR, INSTRUCTOR],
106                 )
107                 | Q(
108                     playlist__organization__user_accesses__user_id=user_id,
109                     playlist__organization__user_accesses__role=ADMINISTRATOR,
110                 )
111             )
112         ).exists()
113 
114     @database_sync_to_async
115     def retrieve_live_session(self):
116         """Guess a live_session from the token and create it id not present."""
117         token = self.scope["token"]
118         if LiveSessionServices.is_lti_token(token):
119             live_session, _ = LiveSessionServices.get_livesession_from_lti(token)
120         else:
121             query_string = parse_qs(self.scope["query_string"])
122             if b"anonymous_id" not in query_string:
123                 raise ConnectionRefusedError()
124             live_session, _ = LiveSessionServices.get_livesession_from_anonymous_id(
125                 anonymous_id=query_string[b"anonymous_id"][0].decode("utf-8"),
126                 video_id=token.payload["resource_id"],
127             )
128 
129         return live_session
130 
131     @database_sync_to_async
132     def update_live_session_with_channel_name(self, live_session):
133         """Update the live_session with the current channel_name."""
134         live_session.channel_name = self.channel_name
135         live_session.save()
136 
137     @database_sync_to_async
138     def reset_live_session(self, live_session):
139         """Reset to None the live_session channel_name."""
140         live_session.channel_name = None
141         live_session.save()
142 
143     async def _is_admin(self):
144         """Check if the connected user has admin permissions."""
145         token = self.scope["token"]
146         if not token:
147             return False
148 
149         if isinstance(token, ResourceAccessToken):
150             return IsTokenInstructor().check_role(token) or IsTokenAdmin().check_role(
151                 token
152             )
153 
154         if isinstance(token, UserAccessToken):
155             return await self._user_has_playlist_or_organization_admin_role(
156                 token.payload.get("user_id"),
157             )
158 
159         raise RuntimeError("Should not be called please check the code.", type(token))
160 
161     async def _get_room_name(self):
162         """Generate the room name the user is connected on depending its permissions."""
163         if await self._is_admin():
164             return defaults.VIDEO_ADMIN_ROOM_NAME.format(video_id=self.__get_video_id())
165 
166         return defaults.VIDEO_ROOM_NAME.format(video_id=self.__get_video_id())
167 
168     # pylint: disable=unused-argument
169     async def disconnect(self, code):
170         """Manage disconnection to this consumer."""
171         # If connection was previously aborted nothing to do
172         if not self.is_connected:
173             return
174 
175         # Leave room group
176         await self.channel_layer.group_discard(self.room_group_name, self.channel_name)
177         if not await self._is_admin():
178             live_session = await self.retrieve_live_session()
179             await self.reset_live_session(live_session)
180 
181     async def video_updated(self, event):
182         """Listener for the video_updated event."""
183         message = {"type": Video.RESOURCE_NAME, "resource": event["video"]}
184         await self.send_json(message)
185 
186     async def thumbnail_updated(self, event):
187         """Listener for the thumbnail updated event."""
188         message = {"type": Thumbnail.RESOURCE_NAME, "resource": event["thumbnail"]}
189         await self.send_json(message)
190 
191     async def timed_text_track_updated(self, event):
192         """Listener for the timed text track updated event."""
193         message = {
194             "type": TimedTextTrack.RESOURCE_NAME,
195             "resource": event["timed_text_track"],
196         }
197         await self.send_json(message)
198 
199     async def shared_live_media_updated(self, event):
200         """Listener for the shared_live_media updated event."""
201         message = {
202             "type": SharedLiveMedia.RESOURCE_NAME,
203             "resource": event["shared_live_media"],
204         }
205         await self.send_json(message)
206
```
--- 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/src/backend/marsha/websocket/consumers/video.py b/src/backend/marsha/websocket/consumers/video.py
--- a/src/backend/marsha/websocket/consumers/video.py
+++ b/src/backend/marsha/websocket/consumers/video.py
@@ -175,8 +175,12 @@
         # Leave room group
         await self.channel_layer.group_discard(self.room_group_name, self.channel_name)
         if not await self._is_admin():
-            live_session = await self.retrieve_live_session()
-            await self.reset_live_session(live_session)
+            try:
+                live_session = await self.retrieve_live_session()
+                await self.reset_live_session(live_session)
+            except ConnectionRefusedError:
+                # No live session found, nothing to do
+                pass
 
     async def video_updated(self, event):
         """Listener for the video_updated event."""

{"golden_diff": "diff --git a/src/backend/marsha/websocket/consumers/video.py b/src/backend/marsha/websocket/consumers/video.py\n--- a/src/backend/marsha/websocket/consumers/video.py\n+++ b/src/backend/marsha/websocket/consumers/video.py\n@@ -175,8 +175,12 @@\n         # Leave room group\n         await self.channel_layer.group_discard(self.room_group_name, self.channel_name)\n         if not await self._is_admin():\n-            live_session = await self.retrieve_live_session()\n-            await self.reset_live_session(live_session)\n+            try:\n+                live_session = await self.retrieve_live_session()\n+                await self.reset_live_session(live_session)\n+            except ConnectionRefusedError:\n+                # No live session found, nothing to do\n+                pass\n \n     async def video_updated(self, event):\n         \"\"\"Listener for the video_updated event.\"\"\"\n", "issue": "Properly clause websocket connection on video/webinar deletion\n## Bug Report\r\n\r\n**Problematic Behavior**\r\n\r\nWhen a video/webinar is deleted, a websocket connection is still open. The dashboard is unmounted by react so it tries to close it but an error is thrown because on closing the connection, the weboscket tries to fetch the attached live session in database but this one does not exists anymore.\r\n\r\n**Expected behavior/code**\r\n\r\nWhen a video/webinar is deleted, the websocket should be properly closed.\r\n\r\n**Steps to Reproduce**\r\n1. Create a webinar\r\n2. Delete it\r\n\r\n\r\n**Additional context/Screenshots**\r\n\r\nhttps://gip-fun-mooc.sentry.io/share/issue/668071c3736a446a890af2b99df1c6c6/\r\n\n", "before_files": [{"content": "\"\"\"Video consumer module\"\"\"\nfrom urllib.parse import parse_qs\n\nfrom django.db.models import Q\n\nfrom channels.db import database_sync_to_async\nfrom channels.generic.websocket import AsyncJsonWebsocketConsumer\n\nfrom marsha.core.models import (\n    ADMINISTRATOR,\n    INSTRUCTOR,\n    SharedLiveMedia,\n    Thumbnail,\n    TimedTextTrack,\n    Video,\n)\nfrom marsha.core.permissions import IsTokenAdmin, IsTokenInstructor\nfrom marsha.core.services import live_session as LiveSessionServices\nfrom marsha.core.simple_jwt.tokens import ResourceAccessToken, UserAccessToken\nfrom marsha.websocket import defaults\n\n\nclass VideoConsumer(AsyncJsonWebsocketConsumer):\n    \"\"\"Video consumer.\"\"\"\n\n    room_group_name = None\n    is_connected = False\n\n    def __get_video_id(self):\n        return self.scope[\"url_route\"][\"kwargs\"][\"video_id\"]\n\n    async def _check_permissions(self):\n        \"\"\"\n        Check if the user has the required permissions.\n\n        Raises:\n            ConnectionRefusedError: if the user does not have the required permissions.\n        \"\"\"\n        token = self.scope[\"token\"]\n        if token is None:\n            raise ConnectionRefusedError()\n\n        # Check permissions, MUST be the same as in the `retrieve` method\n        # of the Video API view set.\n\n        if isinstance(token, ResourceAccessToken):\n            # With LTI: anyone with a valid token for the video can access\n            if token.payload.get(\"resource_id\") != self.__get_video_id():\n                raise ConnectionRefusedError()\n\n        elif isinstance(token, UserAccessToken):\n            # With standalone site, only playlist admin or organization admin can access\n            if not await self._user_has_playlist_or_organization_admin_role(\n                token.payload.get(\"user_id\")\n            ):\n                raise ConnectionRefusedError()\n\n        else:\n            raise RuntimeError(\"This should not happen\")\n\n    async def connect(self):\n        \"\"\"\n        Manage connection to this consumer.\n        During handshake it is not possible to close the websocket with a specific\n        code. To do that we must accept first the connection and then close it with the code we\n        want\n        \"\"\"\n        try:\n            await self._check_permissions()\n            await self._check_video_exists()\n            if not await self._is_admin():\n                live_session = await self.retrieve_live_session()\n                await self.update_live_session_with_channel_name(\n                    live_session=live_session\n                )\n        except ConnectionRefusedError:\n            await self.accept()\n            return await self.close(code=4003)\n\n        self.room_group_name = await self._get_room_name()\n        # Join room group\n        await self.channel_layer.group_add(self.room_group_name, self.channel_name)\n\n        await self.accept()\n        self.is_connected = True\n\n    async def _check_video_exists(self):\n        \"\"\"Close the room if the video does not exists.\"\"\"\n        if not await self._video_exists():\n            raise ConnectionRefusedError()\n\n    @database_sync_to_async\n    def _video_exists(self):\n        \"\"\"Return if a video exists in database or not.\"\"\"\n        return Video.objects.filter(pk=self.__get_video_id()).exists()\n\n    @database_sync_to_async\n    def _user_has_playlist_or_organization_admin_role(self, user_id):\n        \"\"\"Return if the user belongs to the video playlist admin or organization admin.\"\"\"\n        return Video.objects.filter(\n            Q(pk=self.__get_video_id())\n            & (\n                Q(\n                    playlist__user_accesses__user_id=user_id,\n                    playlist__user_accesses__role__in=[ADMINISTRATOR, INSTRUCTOR],\n                )\n                | Q(\n                    playlist__organization__user_accesses__user_id=user_id,\n                    playlist__organization__user_accesses__role=ADMINISTRATOR,\n                )\n            )\n        ).exists()\n\n    @database_sync_to_async\n    def retrieve_live_session(self):\n        \"\"\"Guess a live_session from the token and create it id not present.\"\"\"\n        token = self.scope[\"token\"]\n        if LiveSessionServices.is_lti_token(token):\n            live_session, _ = LiveSessionServices.get_livesession_from_lti(token)\n        else:\n            query_string = parse_qs(self.scope[\"query_string\"])\n            if b\"anonymous_id\" not in query_string:\n                raise ConnectionRefusedError()\n            live_session, _ = LiveSessionServices.get_livesession_from_anonymous_id(\n                anonymous_id=query_string[b\"anonymous_id\"][0].decode(\"utf-8\"),\n                video_id=token.payload[\"resource_id\"],\n            )\n\n        return live_session\n\n    @database_sync_to_async\n    def update_live_session_with_channel_name(self, live_session):\n        \"\"\"Update the live_session with the current channel_name.\"\"\"\n        live_session.channel_name = self.channel_name\n        live_session.save()\n\n    @database_sync_to_async\n    def reset_live_session(self, live_session):\n        \"\"\"Reset to None the live_session channel_name.\"\"\"\n        live_session.channel_name = None\n        live_session.save()\n\n    async def _is_admin(self):\n        \"\"\"Check if the connected user has admin permissions.\"\"\"\n        token = self.scope[\"token\"]\n        if not token:\n            return False\n\n        if isinstance(token, ResourceAccessToken):\n            return IsTokenInstructor().check_role(token) or IsTokenAdmin().check_role(\n                token\n            )\n\n        if isinstance(token, UserAccessToken):\n            return await self._user_has_playlist_or_organization_admin_role(\n                token.payload.get(\"user_id\"),\n            )\n\n        raise RuntimeError(\"Should not be called please check the code.\", type(token))\n\n    async def _get_room_name(self):\n        \"\"\"Generate the room name the user is connected on depending its permissions.\"\"\"\n        if await self._is_admin():\n            return defaults.VIDEO_ADMIN_ROOM_NAME.format(video_id=self.__get_video_id())\n\n        return defaults.VIDEO_ROOM_NAME.format(video_id=self.__get_video_id())\n\n    # pylint: disable=unused-argument\n    async def disconnect(self, code):\n        \"\"\"Manage disconnection to this consumer.\"\"\"\n        # If connection was previously aborted nothing to do\n        if not self.is_connected:\n            return\n\n        # Leave room group\n        await self.channel_layer.group_discard(self.room_group_name, self.channel_name)\n        if not await self._is_admin():\n            live_session = await self.retrieve_live_session()\n            await self.reset_live_session(live_session)\n\n    async def video_updated(self, event):\n        \"\"\"Listener for the video_updated event.\"\"\"\n        message = {\"type\": Video.RESOURCE_NAME, \"resource\": event[\"video\"]}\n        await self.send_json(message)\n\n    async def thumbnail_updated(self, event):\n        \"\"\"Listener for the thumbnail updated event.\"\"\"\n        message = {\"type\": Thumbnail.RESOURCE_NAME, \"resource\": event[\"thumbnail\"]}\n        await self.send_json(message)\n\n    async def timed_text_track_updated(self, event):\n        \"\"\"Listener for the timed text track updated event.\"\"\"\n        message = {\n            \"type\": TimedTextTrack.RESOURCE_NAME,\n            \"resource\": event[\"timed_text_track\"],\n        }\n        await self.send_json(message)\n\n    async def shared_live_media_updated(self, event):\n        \"\"\"Listener for the shared_live_media updated event.\"\"\"\n        message = {\n            \"type\": SharedLiveMedia.RESOURCE_NAME,\n            \"resource\": event[\"shared_live_media\"],\n        }\n        await self.send_json(message)\n", "path": "src/backend/marsha/websocket/consumers/video.py"}], "after_files": [{"content": "\"\"\"Video consumer module\"\"\"\nfrom urllib.parse import parse_qs\n\nfrom django.db.models import Q\n\nfrom channels.db import database_sync_to_async\nfrom channels.generic.websocket import AsyncJsonWebsocketConsumer\n\nfrom marsha.core.models import (\n    ADMINISTRATOR,\n    INSTRUCTOR,\n    SharedLiveMedia,\n    Thumbnail,\n    TimedTextTrack,\n    Video,\n)\nfrom marsha.core.permissions import IsTokenAdmin, IsTokenInstructor\nfrom marsha.core.services import live_session as LiveSessionServices\nfrom marsha.core.simple_jwt.tokens import ResourceAccessToken, UserAccessToken\nfrom marsha.websocket import defaults\n\n\nclass VideoConsumer(AsyncJsonWebsocketConsumer):\n    \"\"\"Video consumer.\"\"\"\n\n    room_group_name = None\n    is_connected = False\n\n    def __get_video_id(self):\n        return self.scope[\"url_route\"][\"kwargs\"][\"video_id\"]\n\n    async def _check_permissions(self):\n        \"\"\"\n        Check if the user has the required permissions.\n\n        Raises:\n            ConnectionRefusedError: if the user does not have the required permissions.\n        \"\"\"\n        token = self.scope[\"token\"]\n        if token is None:\n            raise ConnectionRefusedError()\n\n        # Check permissions, MUST be the same as in the `retrieve` method\n        # of the Video API view set.\n\n        if isinstance(token, ResourceAccessToken):\n            # With LTI: anyone with a valid token for the video can access\n            if token.payload.get(\"resource_id\") != self.__get_video_id():\n                raise ConnectionRefusedError()\n\n        elif isinstance(token, UserAccessToken):\n            # With standalone site, only playlist admin or organization admin can access\n            if not await self._user_has_playlist_or_organization_admin_role(\n                token.payload.get(\"user_id\")\n            ):\n                raise ConnectionRefusedError()\n\n        else:\n            raise RuntimeError(\"This should not happen\")\n\n    async def connect(self):\n        \"\"\"\n        Manage connection to this consumer.\n        During handshake it is not possible to close the websocket with a specific\n        code. To do that we must accept first the connection and then close it with the code we\n        want\n        \"\"\"\n        try:\n            await self._check_permissions()\n            await self._check_video_exists()\n            if not await self._is_admin():\n                live_session = await self.retrieve_live_session()\n                await self.update_live_session_with_channel_name(\n                    live_session=live_session\n                )\n        except ConnectionRefusedError:\n            await self.accept()\n            return await self.close(code=4003)\n\n        self.room_group_name = await self._get_room_name()\n        # Join room group\n        await self.channel_layer.group_add(self.room_group_name, self.channel_name)\n\n        await self.accept()\n        self.is_connected = True\n\n    async def _check_video_exists(self):\n        \"\"\"Close the room if the video does not exists.\"\"\"\n        if not await self._video_exists():\n            raise ConnectionRefusedError()\n\n    @database_sync_to_async\n    def _video_exists(self):\n        \"\"\"Return if a video exists in database or not.\"\"\"\n        return Video.objects.filter(pk=self.__get_video_id()).exists()\n\n    @database_sync_to_async\n    def _user_has_playlist_or_organization_admin_role(self, user_id):\n        \"\"\"Return if the user belongs to the video playlist admin or organization admin.\"\"\"\n        return Video.objects.filter(\n            Q(pk=self.__get_video_id())\n            & (\n                Q(\n                    playlist__user_accesses__user_id=user_id,\n                    playlist__user_accesses__role__in=[ADMINISTRATOR, INSTRUCTOR],\n                )\n                | Q(\n                    playlist__organization__user_accesses__user_id=user_id,\n                    playlist__organization__user_accesses__role=ADMINISTRATOR,\n                )\n            )\n        ).exists()\n\n    @database_sync_to_async\n    def retrieve_live_session(self):\n        \"\"\"Guess a live_session from the token and create it id not present.\"\"\"\n        token = self.scope[\"token\"]\n        if LiveSessionServices.is_lti_token(token):\n            live_session, _ = LiveSessionServices.get_livesession_from_lti(token)\n        else:\n            query_string = parse_qs(self.scope[\"query_string\"])\n            if b\"anonymous_id\" not in query_string:\n                raise ConnectionRefusedError()\n            live_session, _ = LiveSessionServices.get_livesession_from_anonymous_id(\n                anonymous_id=query_string[b\"anonymous_id\"][0].decode(\"utf-8\"),\n                video_id=token.payload[\"resource_id\"],\n            )\n\n        return live_session\n\n    @database_sync_to_async\n    def update_live_session_with_channel_name(self, live_session):\n        \"\"\"Update the live_session with the current channel_name.\"\"\"\n        live_session.channel_name = self.channel_name\n        live_session.save()\n\n    @database_sync_to_async\n    def reset_live_session(self, live_session):\n        \"\"\"Reset to None the live_session channel_name.\"\"\"\n        live_session.channel_name = None\n        live_session.save()\n\n    async def _is_admin(self):\n        \"\"\"Check if the connected user has admin permissions.\"\"\"\n        token = self.scope[\"token\"]\n        if not token:\n            return False\n\n        if isinstance(token, ResourceAccessToken):\n            return IsTokenInstructor().check_role(token) or IsTokenAdmin().check_role(\n                token\n            )\n\n        if isinstance(token, UserAccessToken):\n            return await self._user_has_playlist_or_organization_admin_role(\n                token.payload.get(\"user_id\"),\n            )\n\n        raise RuntimeError(\"Should not be called please check the code.\", type(token))\n\n    async def _get_room_name(self):\n        \"\"\"Generate the room name the user is connected on depending its permissions.\"\"\"\n        if await self._is_admin():\n            return defaults.VIDEO_ADMIN_ROOM_NAME.format(video_id=self.__get_video_id())\n\n        return defaults.VIDEO_ROOM_NAME.format(video_id=self.__get_video_id())\n\n    # pylint: disable=unused-argument\n    async def disconnect(self, code):\n        \"\"\"Manage disconnection to this consumer.\"\"\"\n        # If connection was previously aborted nothing to do\n        if not self.is_connected:\n            return\n\n        # Leave room group\n        await self.channel_layer.group_discard(self.room_group_name, self.channel_name)\n        if not await self._is_admin():\n            try:\n                live_session = await self.retrieve_live_session()\n                await self.reset_live_session(live_session)\n            except ConnectionRefusedError:\n                # No live session found, nothing to do\n                pass\n\n    async def video_updated(self, event):\n        \"\"\"Listener for the video_updated event.\"\"\"\n        message = {\"type\": Video.RESOURCE_NAME, \"resource\": event[\"video\"]}\n        await self.send_json(message)\n\n    async def thumbnail_updated(self, event):\n        \"\"\"Listener for the thumbnail updated event.\"\"\"\n        message = {\"type\": Thumbnail.RESOURCE_NAME, \"resource\": event[\"thumbnail\"]}\n        await self.send_json(message)\n\n    async def timed_text_track_updated(self, event):\n        \"\"\"Listener for the timed text track updated event.\"\"\"\n        message = {\n            \"type\": TimedTextTrack.RESOURCE_NAME,\n            \"resource\": event[\"timed_text_track\"],\n        }\n        await self.send_json(message)\n\n    async def shared_live_media_updated(self, event):\n        \"\"\"Listener for the shared_live_media updated event.\"\"\"\n        message = {\n            \"type\": SharedLiveMedia.RESOURCE_NAME,\n            \"resource\": event[\"shared_live_media\"],\n        }\n        await self.send_json(message)\n", "path": "src/backend/marsha/websocket/consumers/video.py"}]}