]> _ Git - odl.git/commitdiff
WIP #4822 @8
authorStephen Cameron <stephen@cubedesigners.com>
Tue, 23 Nov 2021 22:35:01 +0000 (23:35 +0100)
committerStephen Cameron <stephen@cubedesigners.com>
Tue, 23 Nov 2021 22:35:01 +0000 (23:35 +0100)
resources/js/media-library.js
resources/views/components/media-library/index.blade.php
resources/views/components/media-library/player.blade.php

index 49a1358a7955589edcf496903e9a110dd1c1df99..b3f9522f6ca5598c7f7d5097bbaeffea8ae650ef 100644 (file)
@@ -1,16 +1,27 @@
-// Media Library functionality
+//==============================//
+// Media Library functionality //
+//============================//
+
 export default (options = {}) => ({
     filters: options.filters || {}, // Filters JSON array is passed in from HTML
-    gridSelector: options.gridSelector || false, // CSS selector for main Isotope element
-    filtersOpen: false,
+    filtersOpen: false, // Visibility of the filters overlay
     activeTypeFilters: [],
     activeThemeFilters: [],
-    isotope: {},
-    playerOpen: false,
-    playerType: 'video', // Can be 'video' or 'audio'
-    playerSrc: '',
-    playerPoster: '',
-    playerMIME: '',
+    isotope: {}, // Holds the Isotope instance
+    isotopeOptions: { // Options used for Isotope instantiation (https://isotope.metafizzy.co/options.html)
+        layoutMode: 'fitRows',
+        percentPosition: true,
+    },
+
+    playerOpen: false, // Visibility of the media player overlay
+    player: {}, // Holds the Plyr instance
+    playerOptions: { // Settings used for Plyr instantiation (https://github.com/sampotts/plyr#options)
+        debug: true,
+    },
+
+    /***********\
+    | Filtering |
+    \***********/
 
     get themeFilterList() {
         // Convert active theme filters array into class string for Isotope
@@ -44,17 +55,9 @@ export default (options = {}) => ({
         return list.join();
     },
 
-    init() {
-        if (!this.gridSelector) {
-            console.warn('Error: gridSelector not defined for media grid');
-            return false;
-        }
-
+    initIsotope(element) {
         // Initialise Isotope
-        this.isotope = new Isotope(this.gridSelector, {
-            layoutMode: 'fitRows',
-            percentPosition: true,
-        });
+        this.isotope = new Isotope(element, this.isotopeOptions);
 
         // Update Isotope whenever active filters change
         this.$watch('activeTypeFilters', () => this.applyFilters());
@@ -62,18 +65,47 @@ export default (options = {}) => ({
     },
 
     applyFilters() {
-        console.log('applying filters', this.themeFilterList)
-        this.isotope.arrange({ filter: this.themeFilterList })
+        //console.log('applying filters', this.themeFilterList);
+        this.isotope.arrange({ filter: this.themeFilterList });
     },
 
     removeTypeFilter(filterID) {
         // Remove the filter from the array
-        this.activeTypeFilters = this.activeTypeFilters.filter(item => item !== filterID)
+        this.activeTypeFilters = this.activeTypeFilters.filter(item => item !== filterID);
     },
 
     removeThemeFilter(filterID) {
         // Remove the filter from the array
-        this.activeThemeFilters = this.activeThemeFilters.filter(item => item !== filterID)
+        this.activeThemeFilters = this.activeThemeFilters.filter(item => item !== filterID);
+    },
+
+    /**************\
+    | Media Player |
+    \**************/
+
+    initPlayer(selector) {
+
+        // Get custom controls HTML from the <template> tag
+        this.playerOptions.controls = document.getElementById('playerControls').innerHTML;
+
+        this.player = new Plyr(selector, this.playerOptions);
+        window.player = this.player; // Make player object available in console
+
+        this.player.on('timeupdate', event => {
+            const instance = event.detail.plyr;
+            console.log('timeupdate', instance.currentTime);
+        });
+    },
+
+    openPlayer(sourceData) {
+        this.playerOpen = true;
+        this.player.source = sourceData;
+        this.player.play();
+    },
+
+    closePlayer() {
+        this.player.stop();
+        this.playerOpen = false;
     },
 
 });
index 1beda02bb02dc768a11963d64da52297738dfc6c..42ef8109aae8751eb096ee179cac05f05dbd425b 100644 (file)
 @endpush
 
 {{-- See media_library() definition in resources/js/media-library.js --}}
-<div x-data="media_library({ gridSelector: '.media-grid', filters: {{ json_encode($filters) }} })">
+<div x-data="media_library({ filters: {{ json_encode($filters) }} })">
 
     <x-media-library.filter-list />
 
     {{-- MEDIA LIBRARY GRID --}}
     {{-- Negative margins applied here to offset margins used in Isotope grid --}}
-    <div class="media-grid mt-10 -mb-16 -mx-2.5">
+    <div class="media-grid mt-10 -mb-16 -mx-2.5" x-init="initIsotope($el)">
         @foreach ($media as $item)
-            <div @click="playerOpen = true;
-                         playerType = '{{ $item['type'] }}';
-                         playerSrc = '{{ $item['file'] }}';
-                         playerPoster = '{{ $item['image'] }}';
-                         playerMIME = '{{ $item['mime_type'] }}'"
+            <div @click="openPlayer({
+                     type: '{{ $item['type'] }}',
+                     //poster: '{{ $item['image'] }}',
+                     sources: [{
+                        src: '{{ $item['file'] }}',
+                        type: '{{ $item['mime_type'] }}'
+                     }]
+                 })"
                  class="media-item
                         {{-- Width is 25% minus the gutters (2 * 0.625rem that comes from mx-2.5) --}}
                         float-left w-[calc(25%-1.25rem)] mx-2.5 mb-16
                         media-{{ $item['type'] }}
-                        theme-{{ $item['theme']['id'] }}">
+                        theme-{{ $item['theme']['id'] }}"
+            >
                 <div class="media-item-image relative bg-cover bg-no-repeat rounded-md pb-[56.25%]" style="background-image:url({{ $item['image'] }})">
 
                     {{-- Play Icon --}}
                         </g>
                     </svg>
 
-                    <x-duration class="absolute bottom-2 right-2 font-secondary text-white">{{ $item['duration'] }}</x-duration>
+                    <x-duration class="absolute bottom-2 right-2 font-secondary text-white"
+                                style="text-shadow: rgb(0 0 0 / 50%) 0 0.1em 0.2em">
+                        {{ $item['duration'] }}
+                    </x-duration>
                 </div>
                 {{-- THEME LABEL --}}
                 <div class="mt-2.5 font-secondary font-medium text-xs leading-none" style="color:{{ $item['theme']['color'] }}">
index 8beacd05c858d54120d3a948caab13eff70789e6..359f1a78c33dae60ef962877a44d845624be1840 100644 (file)
      x-transition:leave-end="opacity-0"
      x-cloak>
 
-    <x-close @click.prevent="playerOpen = false; playerType = '';" />
+    <x-close @click.prevent="closePlayer()" />
 
-    <div class="w-full">
+    <div class="w-full" x-init="initPlayer('#player')">
+        {{-- Plyr placeholder --}}
+        {{--
+            Originally this should have been initialised using a simple div (non progressively enhanced method)
+            because the type of player (video or audio) is determined when opening the player and setting the source.
+            However, this doesn't work correctly, so we need to put a placeholder video tag that we can use to
+            initialise the Plyr. The source setter automatically handles switching between video and audio modes.
+            Ref: https://github.com/sampotts/plyr#the-source-setter
 
-        <template x-if="playerType === 'video'">
-            <video class="w-full" x-init="plyr = new Plyr($el)"
-                   {{-- Must call load() on video when changing the source
-                   (ref: https://github.com/alpinejs/alpine/discussions/2180 --}}
-                   x-effect="() => playerSrc && $el.load()"
-                   playsinline autoplay controls :data-poster="playerPoster">
-                <source :src="playerSrc" :type="playerMIME">
-            </video>
-        </template>
+            For the initialisation, I wanted to use the same pattern as for Isotope, ie. x-init="initPlayer($el)" but
+            this fires the initialisation multiple times for some reason. It doesn't seem to be AlpineJS firing it
+            multiple times but I also don't see why Plyr would behave this way when passing a HTMLElement to it.
+            Instead, initialisation is done via ID, called on the wrapping element above: initPlayer('#player')
+        --}}
+        <video id="player" playsinline controls></video>
+    </div>
 
-        <template x-if="playerType === 'audio'">
-            <audio id="player" controls autoplay>
-                <source :src="playerSrc" :type="playerMIME">
-            </audio>
-        </template>
+    {{-- Custom player controls: used when initialising the Plyr --}}
+    {{-- Ref: https://github.com/sampotts/plyr/blob/master/CONTROLS.md --}}
+    <template id="playerControls">
+        <div class="plyr__controls">
 
+            <div class="w-full">
+
+                <div class="plyr__progress">
+                    <input data-plyr="seek" type="range" min="0" max="100" step="0.01" value="0" aria-label="Seek">
+                    <progress class="plyr__progress__buffer" min="0" max="100" value="0">% buffered</progress>
+                    <span role="tooltip" class="plyr__tooltip">00:00</span>
+                </div>
+
+                <div class="flex justify-between items-center">
+
+                    {{-- Left side --}}
+                    <div class="flex items-center">
+                        {{-- Play / Pause button --}}
+                        <button type="button" class="plyr__control" aria-label="Play, {title}" data-plyr="play">
+                            <svg class="icon--pressed" role="presentation"><use xlink:href="#plyr-pause"></use></svg>
+                            <svg class="icon--not-pressed" role="presentation"><use xlink:href="#plyr-play"></use></svg>
+                            <span class="label--pressed plyr__tooltip" role="tooltip">Pause</span>
+                            <span class="label--not-pressed plyr__tooltip" role="tooltip">Play</span>
+                        </button>
+
+                        {{-- Volume Mute --}}
+                        <button type="button" class="plyr__control" aria-label="Mute" data-plyr="mute">
+                            <svg class="icon--pressed" role="presentation"><use xlink:href="#plyr-muted"></use></svg>
+                            <svg class="icon--not-pressed" role="presentation"><use xlink:href="#plyr-volume"></use></svg>
+                            <span class="label--pressed plyr__tooltip" role="tooltip">Unmute</span>
+                            <span class="label--not-pressed plyr__tooltip" role="tooltip">Mute</span>
+                        </button>
+
+                        {{-- Volume slider --}}
+                        <div class="plyr__volume">
+                            <input data-plyr="volume" type="range" min="0" max="1" step="0.05" value="1" autocomplete="off" aria-label="Volume">
+                        </div>
+                    </div>
+
+                    {{-- Right side --}}
+                    <div class="flex items-center">
+                        {{-- Time codes --}}
+                        <div class="plyr__time plyr__time--current" aria-label="Current time">00:00</div>
+                        <div class="plyr__time plyr__time--duration" aria-label="Duration">00:00</div>
+                    </div>
+
+                </div>
+
+                {{-- Fullscreen --}}
+                {{--
+                <button type="button" class="plyr__control" data-plyr="fullscreen">
+                    <svg class="icon--pressed" role="presentation"><use xlink:href="#plyr-exit-fullscreen"></use></svg>
+                    <svg class="icon--not-pressed" role="presentation"><use xlink:href="#plyr-enter-fullscreen"></use></svg>
+                    <span class="label--pressed plyr__tooltip" role="tooltip">Exit fullscreen</span>
+                    <span class="label--not-pressed plyr__tooltip" role="tooltip">Enter fullscreen</span>
+                </button>
+                --}}
+            </div>
+
+        </div>
+    </template>
 
-    </div>
 </div>