-
Notifications
You must be signed in to change notification settings - Fork 0
/
spaceconverter.html
132 lines (125 loc) · 6.6 KB
/
spaceconverter.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="description" content="Twitter Space Dynamic to Master Playlist Converter">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Space Converter</title>
<link rel="shortcut icon" href="https://i.imgur.com/rExrEtv.png" type="image/x-icon">
<style>
*, *:before, *:after {box-sizing: border-box;}
* {margin:0;padding:0;text-decoration: none;outline: none;}
html, body {background-color: #11131a;color: #AAB8C2;font-family: 'Arial', sans-serif;}
a {color: #1da1f2;}
a:hover {color: #77c7f9;transition: all .3s !important;}
.container {display: flex;flex-direction: column;gap:1rem;max-width: 600px;margin: 25px auto;padding: 20px;background-color: #141D26;border-radius: 10px;box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);}
.container.pz {padding: 0;overflow: hidden;}
textarea {height: 100px;padding: 10px;border: 1px solid #243447;border-radius: 5px;background-color: #1B2836;color: #AAB8C2;resize: none;}
.getMaster, .copy {display: block;padding: 10px;background-color: #184d6f;color: #ecf0f1;border: none;border-radius: 5px;cursor: pointer;}
.getMaster:hover, .copy:hover {background-color: #1386cc;transition: all .3s !important;}
.info{ display: flex; align-items: center; gap: 5px;}
ol{ padding: .1rem 2rem 2rem 3rem; display: flex; flex-direction: column; gap: 10px; margin-top: 10px;}
ol li {line-height: 1.7;}
li::marker {color: #928f8f;}
summary {list-style: none; display: flex; justify-content: space-between; align-items: center;font-size: 18px; font-weight: bold; cursor: pointer; user-select: none;padding: 1rem;}
summary::after{ content: ''; width: 0; height: 0; border-top: 10px solid #928f8f; border-inline: 7px solid transparent; transition: 0.2s;}
summary:hover {background: #1b2836;}
details[open] summary {background: #1b2836;}
details[open] > summary::after {transform: rotate(-180deg);}
summary::-webkit-details-marker { display: none;}
code {color: #f1517a;}
mark{ background: none; border-bottom: 2px dotted #1da1f2; color: #AAB8C2; padding-bottom: 1px;}
.error {color: #D8515D!important;border-color: #D8515D!important;}
.success {color: #80C14B;border-color: #80C14B;}
.hts {background: #1B2836;padding: 0 10px;border-radius: 5px;}
.loading {background: transparent!important;display: inline-block;width: 13px;height: 13px;border: 3px solid rgba(255,255,255,.3);border-radius: 50%;border-top-color: #fff;animation: spin 1s ease-in-out infinite;-webkit-animation: spin 1s ease-in-out infinite;}
@keyframes spin {to { -webkit-transform: rotate(360deg); }}
@-webkit-keyframes spin {to { -webkit-transform: rotate(360deg); }}
</style>
</head>
<body>
<div class="container">
<label for="input">Paste Dynamic URL:</code></label>
<textarea id="input" placeholder=".../dynamic_playlist.m3u8?type=live"></textarea>
<button class="getMaster">Get Master URL</button>
<label for="result">Master URL:</label>
<textarea id="result" readonly placeholder="Result will appear here"></textarea>
<div class="info"><button class="copy">Copy</button> the result and paste it to a player <code>(<a href="https://mpv.io" target="_blank">MPV</a>, <a href="https://www.videolan.org/vlc/" target="_blank">VLC</a>)</code>.</div>
</div>
<div class="container pz">
<details>
<summary>How to:</summary>
<ol>
<li>Get <mark>Dynamic URL</mark> while Twitter Space is <mark>live</mark> using <mark>IDM</mark> or extensions like <mark>The Stream Detector</mark> that catches media.<br>Dynamic URL looks like this:<br><code>.../dynamic_playlist.m3u8?type=live</code></li>
<li>Paste <mark>Dynamic URL</mark> to the first box above after the Twitter Space ends.</li>
<li>Click <mark>Get Master URL</mark> button above.</li>
<li>Click <mark>Copy</mark> button above to get <mark>Master URL</mark>.</li>
<li>Paste Master URL to a player like <mark>MPV</mark> or <mark>VLC</mark> to listen.</li>
<li class="hts">If the website doesn't work, use <mark>ctrl+s</mark> to save website to your desktop.<br>Try opening the saved file to use it locally.</li>
</ol>
</details>
</div>
<script>
const corsProxy = "https://cors.viddastrage.workers.dev/corsproxy/?apiurl="
const rEL = document.getElementById('result');
const dEL = document.getElementById('input');
function resultDom(text, cl, clr=false) {
rEL.value = `${text}`;
rEL.classList.add(`${cl}`)
clr && rEL.classList.remove(`${clr}`);
}
document.addEventListener('click', e => {
let getMaster = e.target.closest('.getMaster');
let copy = e.target.closest('.copy');
if (getMaster) {
e.preventDefault();
let dynamicUrl = dEL.value
let masterUrl = dynamicUrl.replace('/dynamic_playlist.m3u8?type=live', '/master_playlist.m3u8');
if (!masterUrl || !masterUrl.includes('/master_playlist.m3u8')) {
resultDom(`Check if Dynamic URL is correct!`, `error`)
return
}
getMaster.innerHTML = `<span class="loading"></span>`;
getXML(masterUrl, true).then(r => {
let data = r.replace(/[\s\S]*(playlist_\d+\.m3u8)[\s\S]*/, '$1');
let playUrl = dynamicUrl.replace('dynamic_playlist.m3u8', data).replace('type=live', 'type=replay');
resultDom(`${playUrl}`, `success`, `error`)
getMaster.innerHTML = `Get Master URL`;
}).catch(error => {
resultDom(`Error: ${error.message}.\r\nCheck 6th info below.`, `error`)
getMaster.innerHTML = `Get Master URL`;
});
}
if (copy) {
e.preventDefault();
let playUrl = rEL.value;
navigator.clipboard.writeText(playUrl).then(() => {
copy.innerText = 'Copied!';
setTimeout(() => {
copy.innerText = 'Copy';
}, 2000);
})
.catch(error => console.error('Error copying to clipboard:', error));
}
});
function getXML(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open("GET", corsProxy+url);
xhr.onload = function () {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.responseText);
} else {
reject(new Error(`HTTP request failed with status ${xhr.status}`));
}
};
xhr.onerror = function () {
reject(new Error('Network error'));
};
xhr.send();
});
}
</script>
</body>
</html>