| 1 |
{% extends "base.html" %} |
| 2 |
|
| 3 |
{% block title %}{{ item.title }} - Library - Makenotwork{% endblock %} |
| 4 |
|
| 5 |
{% block head %} |
| 6 |
<meta name="robots" content="noindex"> |
| 7 |
<link rel="stylesheet" href="/static/media-player.css"> |
| 8 |
{% endblock %} |
| 9 |
|
| 10 |
{% block content %} |
| 11 |
{% include "partials/site_header.html" %} |
| 12 |
|
| 13 |
<article class="media-container"> |
| 14 |
<p class="library-back"> |
| 15 |
<a href="/i/{{ item.id }}">← Store page</a> · |
| 16 |
<a href="/library">Your library</a> |
| 17 |
</p> |
| 18 |
|
| 19 |
<header class="author-header"> |
| 20 |
<div class="author-avatar">{{ creator_avatar_initials }}</div> |
| 21 |
<div class="author-info"> |
| 22 |
<div class="author-name"><a href="/u/{{ creator_username }}">{% if let Some(name) = creator_display_name %}{{ name }}{% else %}{{ creator_username }}{% endif %}</a></div> |
| 23 |
<div class="media-meta">{{ item.release_date }}{% match item.content %}{% when crate::types::ItemContent::Audio with { duration, .. } %}{% if let Some(dur) = duration %} | {{ dur }}{% endif %}{% when _ %}{% endmatch %}</div> |
| 24 |
</div> |
| 25 |
</header> |
| 26 |
|
| 27 |
<h1 class="media-title">{{ item.title }}</h1> |
| 28 |
|
| 29 |
{% if let Some(project_title) = project_title %} |
| 30 |
<p class="media-series"> |
| 31 |
<a href="/p/{{ project_slug }}">{{ project_title }}</a>{% match item.content %}{% when crate::types::ItemContent::Audio with { episode_number, .. } %}{% if let Some(episode) = episode_number %} | Episode {{ episode }}{% endif %}{% when _ %}{% endmatch %} |
| 32 |
</p> |
| 33 |
{% endif %} |
| 34 |
|
| 35 |
<div class="cover-art"> |
| 36 |
{% match item.content %} |
| 37 |
{% when crate::types::ItemContent::Audio with { cover_url, .. } %} |
| 38 |
{% if let Some(url) = cover_url %} |
| 39 |
<img src="{{ url }}" alt="{{ item.title }} cover art"> |
| 40 |
{% endif %} |
| 41 |
{% when _ %} |
| 42 |
{% endmatch %} |
| 43 |
</div> |
| 44 |
|
| 45 |
<div class="media-player"> |
| 46 |
<audio id="media-a" preload="metadata"> |
| 47 |
{% if let Some(audio_url) = audio_url %} |
| 48 |
<source src="{{ audio_url }}" type="audio/mpeg"> |
| 49 |
{% endif %} |
| 50 |
</audio> |
| 51 |
<audio id="media-b" preload="metadata"></audio> |
| 52 |
|
| 53 |
<div class="player-controls"> |
| 54 |
<button class="play-button" id="play-btn" aria-label="Play"> |
| 55 |
<svg id="play-icon" width="24" height="24" viewBox="0 0 24 24" fill="currentColor"> |
| 56 |
<path d="M8 5v14l11-7z"/> |
| 57 |
</svg> |
| 58 |
<svg id="pause-icon" class="media-icon-hidden" width="24" height="24" viewBox="0 0 24 24" fill="currentColor"> |
| 59 |
<path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/> |
| 60 |
</svg> |
| 61 |
</button> |
| 62 |
<div class="progress-container"> |
| 63 |
<div class="progress-bar" id="progress-bar"> |
| 64 |
<div class="progress-fill" id="progress-fill"></div> |
| 65 |
</div> |
| 66 |
<div class="time-display"> |
| 67 |
<span id="current-time">0:00</span> |
| 68 |
<span id="duration">0:00</span> |
| 69 |
</div> |
| 70 |
</div> |
| 71 |
</div> |
| 72 |
|
| 73 |
<div class="insertion-label" id="insertion-label"></div> |
| 74 |
<button class="skip-insertion" id="skip-btn" type="button">Skip ›</button> |
| 75 |
|
| 76 |
<div class="player-secondary"> |
| 77 |
<div class="speed-control"> |
| 78 |
<span>Speed:</span> |
| 79 |
<button class="speed-button" data-speed="0.5">0.5x</button> |
| 80 |
<button class="speed-button is-selected" data-speed="1">1x</button> |
| 81 |
<button class="speed-button" data-speed="1.5">1.5x</button> |
| 82 |
<button class="speed-button" data-speed="2">2x</button> |
| 83 |
</div> |
| 84 |
<div class="volume-control"> |
| 85 |
<span class="volume-icon"> |
| 86 |
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"> |
| 87 |
<path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02z"/> |
| 88 |
</svg> |
| 89 |
</span> |
| 90 |
<input type="range" class="volume-slider" id="volume-slider" min="0" max="100" value="75" /> |
| 91 |
</div> |
| 92 |
</div> |
| 93 |
</div> |
| 94 |
|
| 95 |
{% if !chapters.is_empty() %} |
| 96 |
<div class="chapters"> |
| 97 |
<h3>Chapters</h3> |
| 98 |
<ul class="chapter-list"> |
| 99 |
{% for chapter in chapters %} |
| 100 |
<li class="chapter-item" data-time="{{ chapter.start_seconds }}"> |
| 101 |
<span class="chapter-title">{{ chapter.title }}</span> |
| 102 |
<span class="chapter-time">{{ chapter.timestamp }}</span> |
| 103 |
</li> |
| 104 |
{% endfor %} |
| 105 |
</ul> |
| 106 |
</div> |
| 107 |
{% endif %} |
| 108 |
|
| 109 |
{% if !item.description.is_empty() %} |
| 110 |
<div class="media-description"> |
| 111 |
<p>{{ item.description }}</p> |
| 112 |
</div> |
| 113 |
{% endif %} |
| 114 |
|
| 115 |
{% if !versions.is_empty() %} |
| 116 |
<section class="library-downloads"> |
| 117 |
<h3>Source files</h3> |
| 118 |
{% for version in versions %} |
| 119 |
{% if version.has_file %} |
| 120 |
<div class="download-row"> |
| 121 |
<span class="download-version">v{{ version.number }}</span> |
| 122 |
{% if let Some(label) = version.label %} |
| 123 |
<span class="download-label">{{ label }}</span> |
| 124 |
{% else %} |
| 125 |
<span class="download-label download-label--empty">{% match version.file_name %}{% when Some with (name) %}{{ name }}{% when None %}Download{% endmatch %}</span> |
| 126 |
{% endif %} |
| 127 |
<span class="download-size">{{ version.size }}</span> |
| 128 |
<button class="btn-secondary btn-download-compact" |
| 129 |
onclick="downloadVersion('{{ version.id }}')">Download</button> |
| 130 |
</div> |
| 131 |
{% endif %} |
| 132 |
{% endfor %} |
| 133 |
</section> |
| 134 |
{% endif %} |
| 135 |
</article> |
| 136 |
|
| 137 |
{% include "partials/discussion_section.html" %} |
| 138 |
|
| 139 |
<footer class="media-player-footer"> |
| 140 |
{% if is_owner %} |
| 141 |
<a href="/dashboard/item/{{ item.id }}">Edit</a> · |
| 142 |
{% endif %} |
| 143 |
<a href="/i/{{ item.id }}">Store page</a> · |
| 144 |
<a href="/library">Your library</a> |
| 145 |
</footer> |
| 146 |
{% endblock %} |
| 147 |
|
| 148 |
{% block scripts %} |
| 149 |
<script id="media-player-data" type="application/json"> |
| 150 |
{ |
| 151 |
"segments": {{ segments_json|safe }}, |
| 152 |
"mediaType": "audio", |
| 153 |
"itemId": "{{ item.id }}" |
| 154 |
} |
| 155 |
</script> |
| 156 |
<script src="/static/media-player.js"></script> |
| 157 |
<script> |
| 158 |
function downloadVersion(versionId) { |
| 159 |
fetch('/api/versions/' + versionId + '/download') |
| 160 |
.then(function(res) { |
| 161 |
if (!res.ok) throw new Error('Failed to get download URL'); |
| 162 |
return res.json(); |
| 163 |
}) |
| 164 |
.then(function(data) { |
| 165 |
window.location.href = data.download_url; |
| 166 |
}) |
| 167 |
.catch(function(err) { |
| 168 |
showToast(err.message || 'Download failed'); |
| 169 |
}); |
| 170 |
} |
| 171 |
</script> |
| 172 |
{% endblock %} |
| 173 |
|