@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Implement macros as audio sources

Summary:
Fixes T3887. Basically:

- Macros with audio get passed to the `audio-source` behavior.
- This keeps track of where they are relative to the viewport as the user scrolls.
- When the user scrolls a "once" macro into view, and it reaches roughly the middle of the screen, we play the sound.
- When the user scrolls near a "loop" macro, we start playing the sound at low volume and increase the volume as the user scrolls.

This feels pretty good on both counts.

Test Plan: Tested in Safari, Chrome, and Firefox. FF seems a bit less responsive and doesn't support MP3, but it was fairly nice in Chrome/Safari.

Reviewers: btrahan

Reviewed By: btrahan

CC: aran

Maniphest Tasks: T3887

Differential Revision: https://secure.phabricator.com/D7160

+170 -2
+13
src/__celerity_resource_map__.php
··· 1326 1326 ), 1327 1327 'disk' => '/rsrc/js/core/behavior-more.js', 1328 1328 ), 1329 + 'javelin-behavior-audio-source' => 1330 + array( 1331 + 'uri' => '/res/21831141/rsrc/js/core/behavior-audio-source.js', 1332 + 'type' => 'js', 1333 + 'requires' => 1334 + array( 1335 + 0 => 'javelin-behavior', 1336 + 1 => 'javelin-stratcom', 1337 + 2 => 'javelin-vector', 1338 + 3 => 'javelin-dom', 1339 + ), 1340 + 'disk' => '/rsrc/js/core/behavior-audio-source.js', 1341 + ), 1329 1342 'javelin-behavior-audit-preview' => 1330 1343 array( 1331 1344 'uri' => '/res/d8f31e46/rsrc/js/application/diffusion/behavior-audit-preview.js',
+44 -2
src/applications/macro/remarkup/PhabricatorRemarkupRuleImageMacro.php
··· 25 25 ->withStatus(PhabricatorMacroQuery::STATUS_ACTIVE) 26 26 ->execute(); 27 27 foreach ($rows as $row) { 28 - $this->images[$row->getName()] = $row->getFilePHID(); 28 + $spec = array( 29 + 'image' => $row->getFilePHID(), 30 + ); 31 + 32 + $behavior_none = PhabricatorFileImageMacro::AUDIO_BEHAVIOR_NONE; 33 + if ($row->getAudioPHID()) { 34 + if ($row->getAudioBehavior() != $behavior_none) { 35 + $spec += array( 36 + 'audio' => $row->getAudioPHID(), 37 + 'audioBehavior' => $row->getAudioBehavior(), 38 + ); 39 + } 40 + } 41 + 42 + $this->images[$row->getName()] = $spec; 29 43 } 30 44 } 31 45 32 46 $name = (string)$matches[1]; 33 47 34 48 if (array_key_exists($name, $this->images)) { 35 - $phid = $this->images[$name]; 49 + $phid = $this->images[$name]['image']; 36 50 37 51 $file = id(new PhabricatorFile())->loadOneWhere('phid = %s', $phid); 38 52 ··· 58 72 } 59 73 } 60 74 75 + $id = null; 76 + $audio_phid = idx($this->images[$name], 'audio'); 77 + if ($audio_phid) { 78 + $id = celerity_generate_unique_node_id(); 79 + 80 + $loop = null; 81 + switch (idx($this->images[$name], 'audioBehavior')) { 82 + case PhabricatorFileImageMacro::AUDIO_BEHAVIOR_LOOP: 83 + $loop = true; 84 + break; 85 + } 86 + 87 + $file = id(new PhabricatorFile())->loadOneWhere( 88 + 'phid = %s', 89 + $audio_phid); 90 + if ($file) { 91 + Javelin::initBehavior( 92 + 'audio-source', 93 + array( 94 + 'sourceID' => $id, 95 + 'audioURI' => $file->getBestURI(), 96 + 'loop' => $loop, 97 + )); 98 + } 99 + } 100 + 61 101 $img = phutil_tag( 62 102 'img', 63 103 array( 104 + 'id' => $id, 64 105 'src' => $src_uri, 65 106 'alt' => $matches[1], 66 107 'title' => $matches[1], 67 108 'style' => $style, 68 109 )); 110 + 69 111 return $this->getEngine()->storeText($img); 70 112 } else { 71 113 return $matches[1];
+113
webroot/rsrc/js/core/behavior-audio-source.js
··· 1 + /** 2 + * @provides javelin-behavior-audio-source 3 + * @requires javelin-behavior 4 + * javelin-stratcom 5 + * javelin-vector 6 + * javelin-dom 7 + * @javelin 8 + */ 9 + 10 + /** 11 + * Allows an element to behave as an audio source. It plays a sound either 12 + * when the user scrolls it into view, or loops a sound which gets louder and 13 + * louder as the user gets closer. 14 + */ 15 + JX.behavior('audio-source', function(config, statics) { 16 + if (!window.Audio) { 17 + return; 18 + } 19 + 20 + var audio = new Audio(); 21 + audio.setAttribute('src', config.audioURI); 22 + 23 + if (config.loop) { 24 + audio.setAttribute('loop', true); 25 + } 26 + 27 + audio.load(); 28 + 29 + config.audio = audio; 30 + 31 + statics.items = statics.items || []; 32 + statics.items.push(config); 33 + 34 + if (statics.initialized) { 35 + return; 36 + } 37 + statics.initialized = true; 38 + 39 + var onupdate = function() { 40 + timeout = null; 41 + 42 + var scroll = JX.Vector.getScroll(); 43 + var view = JX.Vector.getViewport(); 44 + var view_mid = scroll.y + (view.y / 2); 45 + 46 + for (var ii = 0; ii < statics.items.length; ii++) { 47 + var item = statics.items[ii]; 48 + if (!item.element) { 49 + try { 50 + item.element = JX.$(item.sourceID); 51 + } catch (ignored) { 52 + continue; 53 + } 54 + } 55 + 56 + var pos = JX.Vector.getPos(statics.items[ii].element); 57 + var dim = JX.Vector.getDim(statics.items[ii].element); 58 + 59 + var item_mid = pos.y + (dim.y / 2); 60 + var item_distance = Math.abs(item_mid - view_mid); 61 + 62 + // item_distance is the number of pixels between the vertical middle 63 + // of the macro and the vertical middle of the viewport. We divide it 64 + // by the viewport height to get the "number of viewports" away from 65 + // the middle we are, then map that to [0, 1], where 0 means that the 66 + // image is far away from the viewport and 1 means the image is pretty 67 + // much in the middle of the viewport. 68 + 69 + var near = 1.25 - ((item_distance / view.y) * 1.25); 70 + near = Math.max(0, near); 71 + near = Math.min(1, near); 72 + 73 + if (near === 0) { 74 + if (item.playing) { 75 + item.audio.pause(); 76 + item.playing = false; 77 + 78 + // If this isn't an ambient/looping sound, it only gets to fire 79 + // once. Even if it didn't finish, throw it out. 80 + if (!item.loop) { 81 + statics.items.splice(ii, 1); 82 + ii--; 83 + } 84 + } 85 + continue; 86 + } else { 87 + if (!item.playing) { 88 + if (!item.loop && near < 1) { 89 + // Don't start playing one-shot items until they're solidly on 90 + // screen. 91 + continue; 92 + } 93 + item.audio.volume = near; 94 + item.playing = true; 95 + item.audio.play(); 96 + } else { 97 + item.audio.volume = near; 98 + } 99 + } 100 + } 101 + 102 + }; 103 + 104 + var timeout; 105 + var onadjust = function() { 106 + timeout && clearTimeout(timeout); 107 + timeout = setTimeout(onupdate, 200); 108 + }; 109 + 110 + JX.Stratcom.listen(['scroll', 'resize'], null, onadjust); 111 + onadjust(); 112 + 113 + });