0 Votes

Wiki source code of Uncategorized Videos

Version 498.1 by Ryan C on 2025/09/10 04:47

Hide last authors
Ryan C 493.1 1 {{velocity}}
Ryan C 498.1 2 ## Gather video attachments on the current page
3 #set($videoExtensions = ['mp4','webm','ogg','avi','mov','wmv','flv','m4v'])
4 #set($videos = [])
Ryan C 493.1 5 #foreach($att in $doc.getAttachmentList())
Ryan C 498.1 6 #set($name = $att.getFilename())
7 #set($lname = $name.toLowerCase())
8 #foreach($ext in $videoExtensions)
9 #if($lname.endsWith("." + $ext))
Ryan C 493.1 10 #set($discard = $videos.add($att))
11 #break
12 #end
13 #end
14 #end
15
Ryan C 498.1 16 {{cache id="vid-list-$doc.fullName" timeToLive="21600"}}## 6h cache of rendered HTML
Ryan C 493.1 17 {{html wiki="false" clean="false"}}
18 <div id="xwiki-video-manager" style="margin:20px 0;">
Ryan C 494.1 19 <h2>📹 Videos on: ${escapetool.xml($doc.fullName)}</h2>
Ryan C 493.1 20
Ryan C 494.1 21 #if($videos.size() == 0)
22 <div style="text-align:center;padding:40px;background:#f8f9fa;border-radius:8px;">
23 <h3>No Videos Found</h3>
24 <p>Attach video files to this page to see them here.</p>
Ryan C 493.1 25 </div>
Ryan C 494.1 26 #else
Ryan C 498.1 27 ## ---- Settings
Ryan C 494.1 28 <script>
Ryan C 498.1 29 window.VID_CHUNK_SIZE = 48; // how many lightweight cards per chunk
30 window.VID_LAZY_MARGIN = '600px';// start loading a bit before entering view
Ryan C 494.1 31 </script>
Ryan C 493.1 32
Ryan C 494.1 33 <div id="video-chunks">
34 #set($i = 0)
35 #set($chunkIndex = 0)
36 #foreach($att in $videos)
37 #set($i = $i + 1)
38 #set($filename = $att.getFilename())
39 #set($lname = $filename.toLowerCase())
40 #set($url = $doc.getAttachmentURL($filename))
Ryan C 493.1 41
Ryan C 498.1 42 ## decide MIME type (lightweight, used later when creating <video>)
Ryan C 493.1 43 #set($videoType = "video/mp4")
Ryan C 494.1 44 #if($lname.endsWith(".webm"))
45 #set($videoType = "video/webm")
46 #elseif($lname.endsWith(".ogg"))
47 #set($videoType = "video/ogg")
48 #elseif($lname.endsWith(".avi"))
49 #set($videoType = "video/x-msvideo")
50 #elseif($lname.endsWith(".mov"))
51 #set($videoType = "video/quicktime")
52 #elseif($lname.endsWith(".wmv"))
53 #set($videoType = "video/x-ms-wmv")
54 #elseif($lname.endsWith(".flv"))
55 #set($videoType = "video/x-flv")
56 #elseif($lname.endsWith(".m4v"))
57 #set($videoType = "video/mp4")
58 #end
Ryan C 493.1 59
Ryan C 498.1 60 ## open chunk wrapper when starting a new chunk
Ryan C 494.1 61 #if($i == 1 || ($i - 1) % 48 == 0)
62 #set($chunkIndex = $chunkIndex + 1)
63 <div class="vid-chunk" data-chunk="$chunkIndex" style="display: #if($chunkIndex == 1) block #else none #end;">
64 <div class="video-display-grid" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:20px;">
65 #end
Ryan C 493.1 66
Ryan C 498.1 67 ## lightweight card (NO <video> yet)
Ryan C 494.1 68 <div class="video-container" style="border:1px solid #ddd;border-radius:8px;padding:12px;background:#fff;">
69 <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;">
70 <h4 style="margin:0;flex:1;min-width:0;">${escapetool.xml($filename)}</h4>
71 #if($xcontext.action == 'edit')
72 <input type="checkbox" class="video-selector" data-video="${escapetool.xml($filename)}" title="Select for bulk actions">
73 #end
74 </div>
Ryan C 493.1 75
Ryan C 498.1 76 <!-- Placeholder; the real <video> is created on demand -->
Ryan C 494.1 77 <div class="video-frame"
78 data-src="${url}"
79 data-type="${videoType}"
80 data-name="${escapetool.xml($filename)}"
Ryan C 498.1 81 style="width:100%;aspect-ratio:16/9;background:#f3f3f3;border-radius:4px;display:flex;align-items:center;justify-content:center;cursor:pointer;">
82 <button class="btn btn-sm btn-primary" type="button">Load &amp; Play</button>
Ryan C 494.1 83 </div>
84
Ryan C 498.1 85 <div class="video-controls" style="margin-top:8px;display:flex;flex-wrap:wrap;gap:6px;">
Ryan C 494.1 86 <a href="${url}" download="${escapetool.xml($filename)}" class="btn btn-sm btn-success">📥 Download</a>
87 <span class="vid-duration" style="font-size:12px;color:#666;">Duration: —</span>
88 </div>
Ryan C 493.1 89 </div>
90
Ryan C 498.1 91 ## close chunk wrapper when reaching size or end
Ryan C 494.1 92 #if(($i % 48 == 0) || $foreach.last)
93 </div>
94 #if(!$foreach.last)
95 <div style="text-align:center;margin:12px 0 28px;">
96 <button class="btn btn-secondary load-more" data-next="$mathtool.add($chunkIndex,1)">Load more</button>
97 </div>
98 #end
99 </div>
100 #end
101 #end
102 </div>
103 #end
Ryan C 493.1 104 </div>
105
106 <script>
Ryan C 494.1 107 (function(){
Ryan C 498.1 108 // Utility: create a <video preload="none"> only when needed
109 function mountVideo(frame){
110 if(frame.getAttribute('data-mounted') === '1') return;
Ryan C 494.1 111 const src = frame.getAttribute('data-src');
112 const type = frame.getAttribute('data-type') || 'video/mp4';
Ryan C 498.1 113 const name = frame.getAttribute('data-name') || 'video';
Ryan C 494.1 114
Ryan C 498.1 115 const v = document.createElement('video');
116 v.setAttribute('controls','');
117 v.setAttribute('preload','none'); // don't fetch until user interacts or we call load()
118 v.style.width = '100%';
119 v.style.maxWidth = '100%';
120 v.style.borderRadius = '4px';
Ryan C 494.1 121
Ryan C 498.1 122 const s = document.createElement('source');
123 s.src = src;
124 s.type = type;
125 v.appendChild(s);
Ryan C 494.1 126
Ryan C 498.1 127 // when metadata arrives (after load()), fill duration text
Ryan C 494.1 128 v.addEventListener('loadedmetadata', function(){
Ryan C 498.1 129 const d = Math.round(v.duration||0);
130 const mm = Math.floor(d/60), ss = String(d%60).padStart(2,'0');
131 const dur = frame.parentElement.querySelector('.vid-duration');
132 if(dur) dur.textContent = 'Duration: ' + mm + ':' + ss;
Ryan C 493.1 133 });
Ryan C 498.1 134
Ryan C 494.1 135 frame.replaceChildren(v);
136 frame.setAttribute('data-mounted','1');
Ryan C 498.1 137 // We don't call v.load() here; it will start when the user clicks play.
Ryan C 494.1 138 }
139
Ryan C 498.1 140 // Click-to-load for each placeholder
141 document.querySelectorAll('.video-frame').forEach(function(f){
142 f.addEventListener('click', function(){
143 mountVideo(f);
144 const v = f.querySelector('video');
145 if(v) v.play().catch(()=>{});
146 }, {once:false});
147 });
148
149 // IntersectionObserver to auto-mount when approaching viewport
Ryan C 494.1 150 if('IntersectionObserver' in window){
151 const io = new IntersectionObserver((entries)=>{
152 entries.forEach(e=>{
153 if(e.isIntersecting){
Ryan C 498.1 154 mountVideo(e.target);
155 // don't auto-play on scroll; user can play if desired
Ryan C 494.1 156 io.unobserve(e.target);
157 }
158 });
159 }, { rootMargin: (window.VID_LAZY_MARGIN||'600px') });
160 document.querySelectorAll('.video-frame').forEach(el=>io.observe(el));
Ryan C 493.1 161 }
Ryan C 494.1 162
Ryan C 498.1 163 // Chunk loader (reveals next batch only when user asks)
Ryan C 494.1 164 document.addEventListener('click', function(ev){
Ryan C 498.1 165 const b = ev.target.closest('.load-more');
166 if(!b) return;
Ryan C 494.1 167 const next = b.getAttribute('data-next');
Ryan C 498.1 168 const nxt = document.querySelector('.vid-chunk[data-chunk="'+ next +'"]');
Ryan C 494.1 169 if(nxt){ nxt.style.display = 'block'; b.parentElement.style.display = 'none'; }
170 });
171
Ryan C 498.1 172 // Basic button styles (same as before)
Ryan C 494.1 173 })();
Ryan C 493.1 174 </script>
175
176 <style>
Ryan C 494.1 177 .video-container:hover{box-shadow:0 4px 8px rgba(0,0,0,0.08);transition:box-shadow .25s;}
Ryan C 493.1 178 .btn{padding:4px 8px;border:1px solid #ddd;background:#f8f9fa;border-radius:4px;cursor:pointer;text-decoration:none;display:inline-block;}
179 .btn:hover{background:#e9ecef;}
180 .btn-primary{background:#007bff;color:#fff;border-color:#007bff;}
181 .btn-secondary{background:#6c757d;color:#fff;border-color:#6c757d;}
182 .btn-success{background:#28a745;color:#fff;border-color:#28a745;}
183 .btn-sm{font-size:12px;padding:2px 6px;}
Ryan C 494.1 184 @media (max-width:768px){.video-display-grid{grid-template-columns:1fr}}
Ryan C 493.1 185 </style>
186 {{/html}}
Ryan C 494.1 187 {{/cache}}
Ryan C 493.1 188 {{/velocity}}
Ryan C 498.1 189