pyLiveMusic 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,6 @@
1
+ from .core import pyLiveMusic
2
+
3
+ __version__ = "0.0.1" # MAJOR.MINOR.PATCH
4
+
5
+ __author__ = "github.com/Bakachii"
6
+ __all__ = ["pyLiveMusic"]
@@ -0,0 +1,17 @@
1
+ from aiohttp import web
2
+
3
+
4
+ ERROR_STATUS = {
5
+ "NOT_PLAYING_ANYTHING_CURRENTLY": 409,
6
+ "INVALID_SEEK_LIMIT": 400,
7
+ "INVALID_SEEK_SECONDS": 400,
8
+ "INVALID_VOLUME": 400,
9
+ "INVALID_REPEAT_MODE": 400,
10
+ "INVALID_PLAY_ORDER": 400,
11
+ "INVALID_AUDIO_QUALITY": 400,
12
+ "TRACK_DURATION_UNKNOWN": 409,
13
+ }
14
+
15
+
16
+ def json_error(code):
17
+ return web.json_response({"error": code}, status=ERROR_STATUS.get(code, 400))
@@ -0,0 +1,213 @@
1
+ from aiohttp import web
2
+
3
+
4
+ from pyLiveMusic._api._errors import json_error
5
+
6
+
7
+ def get_room(request):
8
+
9
+ room = request.app["state"].rooms.get(request.match_info["room_id"])
10
+ if room is None:
11
+ raise web.HTTPNotFound(text="ROOM_NOT_FOUND")
12
+
13
+ return room
14
+
15
+
16
+ async def pause(request):
17
+
18
+ request.app["auth"]._verify(request)
19
+ room = get_room(request)
20
+
21
+ try:
22
+ await room.pause()
23
+ except ValueError as error:
24
+ return json_error(str(error))
25
+
26
+ return web.json_response(room.state())
27
+
28
+
29
+ async def resume(request):
30
+
31
+ request.app["auth"]._verify(request)
32
+ room = get_room(request)
33
+
34
+ try:
35
+ await room.resume()
36
+ except ValueError as error:
37
+ return json_error(str(error))
38
+
39
+ return web.json_response(room.state())
40
+
41
+
42
+ async def skip(request):
43
+
44
+ request.app["auth"]._verify(request)
45
+ room = get_room(request)
46
+
47
+ try:
48
+ await room.skip()
49
+ except ValueError as error:
50
+ return json_error(str(error))
51
+
52
+ return web.json_response(room.state_or_not_playing())
53
+
54
+
55
+ async def volume(request):
56
+
57
+ request.app["auth"]._verify(request)
58
+ room = get_room(request)
59
+ data = await request.json()
60
+
61
+ try:
62
+ await room.set_volume(data["volume"])
63
+
64
+ except (
65
+ KeyError,
66
+ ValueError,
67
+ ) as error:
68
+
69
+ code = (
70
+ str(error)
71
+ if isinstance(error, ValueError)
72
+ else "INVALID_VOLUME"
73
+ )
74
+
75
+ return json_error(code)
76
+
77
+ return web.json_response(room.state_or_not_playing())
78
+
79
+
80
+ async def shuffle(request):
81
+
82
+ request.app["auth"]._verify(request)
83
+ room = get_room(request)
84
+ data = await request.json()
85
+
86
+ await room.set_shuffle(data.get("enabled", False))
87
+
88
+ return web.json_response(room.state_or_not_playing())
89
+
90
+
91
+ async def repeat(request):
92
+
93
+ request.app["auth"]._verify(request)
94
+ room = get_room(request)
95
+ data = await request.json()
96
+
97
+ try:
98
+ await room.set_repeat(data["mode"])
99
+
100
+ except (
101
+ KeyError,
102
+ ValueError,
103
+ ) as error:
104
+
105
+ code = (
106
+ str(error)
107
+ if isinstance(error, ValueError)
108
+ else "INVALID_REPEAT_MODE"
109
+ )
110
+
111
+ return json_error(code)
112
+
113
+ return web.json_response(room.state_or_not_playing())
114
+
115
+
116
+ async def ascending(request):
117
+
118
+ request.app["auth"]._verify(request)
119
+ room = get_room(request)
120
+
121
+ try:
122
+ await room.set_order("ascending")
123
+ except ValueError as error:
124
+ return json_error(str(error))
125
+
126
+ return web.json_response(room.state_or_not_playing())
127
+
128
+
129
+ async def descending(request):
130
+
131
+ request.app["auth"]._verify(request)
132
+ room = get_room(request)
133
+
134
+ try:
135
+ await room.set_order("descending")
136
+ except ValueError as error:
137
+ return json_error(str(error))
138
+
139
+ return web.json_response(room.state_or_not_playing())
140
+
141
+
142
+ async def seek_front(request):
143
+
144
+ request.app["auth"]._verify(request)
145
+ room = get_room(request)
146
+ data = await request.json()
147
+
148
+ try:
149
+ await room.seek_front(data["seconds"])
150
+
151
+ except (
152
+ KeyError,
153
+ ValueError,
154
+ ) as error:
155
+
156
+ code = (
157
+ str(error)
158
+ if isinstance(error, ValueError)
159
+ else "INVALID_SEEK_SECONDS"
160
+ )
161
+
162
+ return json_error(code)
163
+
164
+ return web.json_response(room.state())
165
+
166
+
167
+ async def seek_back(request):
168
+
169
+ request.app["auth"]._verify(request)
170
+ room = get_room(request)
171
+ data = await request.json()
172
+
173
+ try:
174
+ await room.seek_back(data["seconds"])
175
+ except (
176
+ KeyError,
177
+ ValueError,
178
+ ) as error:
179
+
180
+ code = (
181
+ str(error)
182
+ if isinstance(error, ValueError)
183
+ else "INVALID_SEEK_SECONDS"
184
+ )
185
+
186
+ return json_error(code)
187
+
188
+ return web.json_response(room.state())
189
+
190
+
191
+ async def quality(request):
192
+
193
+ request.app["auth"]._verify(request)
194
+ room = get_room(request)
195
+ data = await request.json()
196
+
197
+ try:
198
+ await room.set_quality(data["quality"])
199
+
200
+ except (
201
+ KeyError,
202
+ ValueError,
203
+ ) as error:
204
+
205
+ code = (
206
+ str(error)
207
+ if isinstance(error, ValueError)
208
+ else "INVALID_AUDIO_QUALITY"
209
+ )
210
+
211
+ return json_error(code)
212
+
213
+ return web.json_response(room.state_or_not_playing())
@@ -0,0 +1,106 @@
1
+ from aiohttp import web
2
+
3
+
4
+ async def get_queue(request):
5
+
6
+ room = request.app["state"].rooms.get(request.match_info["room_id"])
7
+ if room is None:
8
+ raise web.HTTPNotFound(text="ROOM_NOT_FOUND")
9
+
10
+ return web.json_response({
11
+ "room_id": room.id,
12
+ "current":
13
+ (
14
+ {
15
+ "id": room.current.id,
16
+ "title": room.current.title,
17
+ }
18
+ if room.current
19
+ else None
20
+ ),
21
+
22
+ "queue": [
23
+ {
24
+ "id": item.id,
25
+ "title": item.title,
26
+ "path": item.path,
27
+ }
28
+
29
+ for item in room.queue
30
+ ],
31
+ })
32
+
33
+
34
+ async def add_track(request):
35
+
36
+ request.app["auth"]._verify(request)
37
+
38
+ room = request.app["state"].rooms.get(request.match_info["room_id"])
39
+ if room is None:
40
+ raise web.HTTPNotFound(text="ROOM_NOT_FOUND")
41
+
42
+ data = await request.json()
43
+
44
+ item = await room.add_track(
45
+ title=data["title"],
46
+ path=data["path"],
47
+ )
48
+
49
+ return web.json_response(
50
+ {
51
+ "id": item.id,
52
+ "title": item.title,
53
+ },
54
+ status=201,
55
+ )
56
+
57
+
58
+ async def remove_track(request):
59
+
60
+ request.app["auth"]._verify(request)
61
+
62
+ room = request.app["state"].rooms.get(request.match_info["room_id"])
63
+ if room is None:
64
+ raise web.HTTPNotFound(text="ROOM_NOT_FOUND")
65
+
66
+ track_id = request.match_info["track_id"]
67
+ before = len(room.queue)
68
+
69
+ room.queue = [
70
+ item
71
+ for item in room.queue
72
+ if item.id != track_id
73
+ ]
74
+
75
+ if len(room.queue) == before:
76
+ raise web.HTTPNotFound(text="TRACK_NOT_FOUND")
77
+
78
+ return web.json_response(
79
+ {
80
+ "queue": [
81
+ {
82
+ "id": item.id,
83
+ "title": item.title,
84
+ }
85
+
86
+ for item in room.queue
87
+ ]
88
+ }
89
+ )
90
+
91
+
92
+ async def clear_queue(request):
93
+ request.app["auth"]._verify(request)
94
+
95
+ room = request.app["state"].rooms.get(request.match_info["room_id"])
96
+ if room is None:
97
+ raise web.HTTPNotFound(text="ROOM_NOT_FOUND")
98
+
99
+ room.queue.clear()
100
+
101
+ if room.current is None:
102
+ room.schedule_delete()
103
+
104
+ return web.json_response({
105
+ "queue": []
106
+ })
@@ -0,0 +1,51 @@
1
+ from aiohttp import web
2
+
3
+
4
+ async def create_room(request):
5
+ request.app["auth"]._verify(request)
6
+ user_id = request.app["auth"]._get_auth_key()
7
+
8
+ data = await request.json()
9
+
10
+ name = data.get(
11
+ "name",
12
+ "Private Room",
13
+ )
14
+
15
+ rooms = request.app["state"].rooms
16
+
17
+ room = await rooms.create(
18
+ name=name,
19
+ controllers={user_id},
20
+ )
21
+
22
+ return web.json_response(
23
+ room.state(),
24
+ status=201,
25
+ )
26
+
27
+
28
+ async def get_room(request):
29
+ room_id = request.match_info["room_id"]
30
+ room = request.app["state"].rooms.get(room_id)
31
+
32
+ if room is None:
33
+ raise web.HTTPNotFound(text="ROOM_NOT_FOUND")
34
+
35
+ return web.json_response(room.state())
36
+
37
+
38
+ async def end_room(request):
39
+
40
+ request.app["auth"]._verify(request)
41
+
42
+ room_id = request.match_info["room_id"]
43
+ rooms = request.app["state"].rooms
44
+ room = rooms.get(room_id)
45
+
46
+ if room is None:
47
+ raise web.HTTPNotFound(text="ROOM_NOT_FOUND")
48
+
49
+ await rooms.remove(room_id)
50
+
51
+ return web.json_response({"status": "ended"})
@@ -0,0 +1,24 @@
1
+ from aiohttp import web
2
+
3
+
4
+ async def offer(request):
5
+ room_id = request.match_info["room_id"]
6
+ room = request.app["state"].rooms.get(room_id)
7
+
8
+ if room is None:
9
+ raise web.HTTPNotFound(text="Room not found")
10
+
11
+ data = await request.json()
12
+ peer = await room.add_peer()
13
+
14
+ try:
15
+ answer = await room.offer(peer, data["sdp"])
16
+ except Exception:
17
+ await room.remove_peer(peer)
18
+
19
+ raise
20
+
21
+ return web.json_response({
22
+ "type": answer.type,
23
+ "sdp": answer.sdp,
24
+ })
@@ -0,0 +1,34 @@
1
+ from pyLiveMusic._webrtc.backend import RoomManager
2
+
3
+ from pyLiveMusic._core._database.mongo._storage import _MongoStorage
4
+ from pyLiveMusic._core._database.mongo._repo import MongoRoomRepository, MongoUserRepository
5
+ from pyLiveMusic._core._database.memory._repo import MemoryRoomRepository, MemoryUserRepository
6
+
7
+
8
+ class _core_state:
9
+ def __init__(self):
10
+ self.rooms = None
11
+ self.storage = None
12
+ self.users = None
13
+ self.room_repository = None
14
+
15
+ def MemoryState(self, storage):
16
+ self.storage = storage.storage
17
+ self.users = MemoryUserRepository(self.storage.users)
18
+ self.room_repository = MemoryRoomRepository(self.storage.rooms)
19
+ self.rooms = RoomManager(self.room_repository)
20
+
21
+ def MongoDBState(self, storage):
22
+ self.storage = _MongoStorage(
23
+ storage.db_url,
24
+ storage.db_name,
25
+ storage.user_collection,
26
+ storage.room_collection,
27
+ )
28
+
29
+ self.users = MongoUserRepository(self.storage.users)
30
+ self.room_repository = MongoRoomRepository(self.storage.rooms)
31
+ self.rooms = RoomManager(self.room_repository)
32
+
33
+ def RedisState(self, storage):
34
+ raise NotImplementedError("Redis storage is not implemented yet")
@@ -0,0 +1,26 @@
1
+ from pyLiveMusic._core._core_func._data_config import _core_state
2
+
3
+ from pyLiveMusic.storage import Memory, MongoDB, Redis
4
+
5
+
6
+ class _data_state:
7
+
8
+ def __init__(self, storage):
9
+ state_container = _core_state()
10
+
11
+ if isinstance(storage, Memory):
12
+ state_container.MemoryState(storage)
13
+
14
+ elif isinstance(storage, MongoDB):
15
+ state_container.MongoDBState(storage)
16
+
17
+ elif isinstance(storage, Redis):
18
+ state_container.RedisState(storage)
19
+
20
+ else:
21
+ raise TypeError("Unsupported storage type")
22
+
23
+ self.rooms = state_container.rooms
24
+ self.storage = state_container.storage
25
+ self.users = state_container.users
26
+ self.room_repository = state_container.room_repository
@@ -0,0 +1,41 @@
1
+ class MemoryRoomRepository:
2
+
3
+ def __init__(self, rooms):
4
+ self.rooms = rooms
5
+
6
+ async def create(self, room_id, name, controllers):
7
+ document = {
8
+ "_id": room_id,
9
+ "name": name,
10
+ "controllers": list(controllers),
11
+ }
12
+
13
+ self.rooms[room_id] = document
14
+ return document
15
+
16
+ async def get(self, room_id):
17
+ return self.rooms.get(room_id)
18
+
19
+ async def list(self):
20
+ return list(self.rooms.values())
21
+
22
+ async def delete(self, room_id):
23
+ self.rooms.pop(room_id, None)
24
+
25
+ class MemoryUserRepository:
26
+
27
+ def __init__(self, users):
28
+ self.users = users
29
+
30
+ async def get(self, user_id):
31
+ return self.users.get(user_id)
32
+
33
+ async def create(self, user_id, username):
34
+ document = {
35
+ "_id": user_id,
36
+ "username": username
37
+ }
38
+
39
+ self.users[user_id] = document
40
+
41
+ return document
@@ -0,0 +1,13 @@
1
+ class _MemoryStorage:
2
+ def __init__(self):
3
+ self.users = {}
4
+ self.rooms = {}
5
+
6
+ async def connect(self):
7
+ print("Memory storage initialized")
8
+
9
+ async def close(self):
10
+ self.users.clear()
11
+ self.rooms.clear()
12
+
13
+ print("Memory storage cleared")
@@ -0,0 +1,45 @@
1
+ class MongoRoomRepository:
2
+
3
+ def __init__(self, collection):
4
+ self.collection = collection
5
+
6
+ async def create(self, room_id, name, controllers):
7
+ document = {
8
+ "_id": room_id,
9
+ "name": name,
10
+ "controllers": list(controllers),
11
+ }
12
+
13
+ await self.collection.insert_one(document)
14
+ return document
15
+
16
+ async def get(self, room_id):
17
+ return await self.collection.find_one({"_id": room_id})
18
+
19
+ async def list(self):
20
+ return [document async for document in self.collection.find({})]
21
+
22
+ async def delete(self, room_id):
23
+ await self.collection.delete_one({"_id": room_id})
24
+
25
+
26
+ class MongoUserRepository:
27
+
28
+ def __init__(self, collection):
29
+
30
+ self.collection = collection
31
+
32
+ async def get(self, user_id):
33
+ return await self.collection.find_one({"_id": user_id})
34
+
35
+ async def create(
36
+ self,
37
+ user_id,
38
+ username,
39
+ ):
40
+
41
+ document = {"_id": user_id, "username": username}
42
+
43
+ await self.collection.insert_one(document)
44
+
45
+ return document
@@ -0,0 +1,31 @@
1
+ class _MongoStorage:
2
+ from pymongo import AsyncMongoClient
3
+
4
+ def __init__(
5
+ self,
6
+ db_url: str,
7
+ db_name: str,
8
+ user_collection: str,
9
+ room_collection: str
10
+ ):
11
+ try:
12
+ from pymongo import AsyncMongoClient
13
+ except ImportError as exc:
14
+ raise ImportError(
15
+ "To use the MongoDB, you must install pyLiveMusic via "
16
+ "pip install 'pyLiveMusic[mongo]'"
17
+ ) from exc
18
+ self.client = AsyncMongoClient(db_url)
19
+ self.storage = self.client[db_name]
20
+
21
+ self.users = self.storage[user_collection]
22
+ self.rooms = self.storage[room_collection]
23
+
24
+
25
+ async def connect(self):
26
+ await self.client.admin.command("ping")
27
+ print("MongoDB connected")
28
+
29
+ async def close(self):
30
+ await self.client.close()
31
+ print("MongoDB disconnected")
@@ -0,0 +1,5 @@
1
+ class RedisRoomRepository:
2
+ pass
3
+
4
+ class RedisUserRepository:
5
+ pass
@@ -0,0 +1,3 @@
1
+ class _RedisStorage:
2
+ def __init__(self):
3
+ pass
@@ -0,0 +1,18 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Live Stream</title>
7
+ </head>
8
+
9
+ <body>
10
+ <audio id="audio" controls autoplay playsinline preload="auto" controlslist="nodownload noplaybackrate"></audio>
11
+ <br>
12
+ <label for="volume">Volume</label>
13
+ <input id="volume" type="range" min="0" max="1" step="0.01" value="1">
14
+ <span id="status">CONNECTING...</span>
15
+ <script src="/static/audio.js?v=3"></script>
16
+ </body>
17
+
18
+ </html>