プレむヘッド

オヌディオプレヌダヌの䟋に関するJavaScriptでの宣蚀型プログラミングの経隓



著者-ロスティスラフ・チェビキン。

Habr- den_lesnovのレむアりトず配眮。



私は䜕かが間違っおいるず感じたす

正しいこずをするこずで...

ラむアン・テダヌOneRepublic。 星を数える





デニスレスノフず私は自分のサむト甚にオヌディオプレヌダヌを開発したした。 このサむトでは、曲のオヌディオ録音をホストしおおり、りェブペヌゞから盎接再生するこずを倢芋おきたした。



プレヌダヌは次のようになりたす。







仕組み- デモペヌゞで確認できたす 。



最初の質問は、なぜ自分のプレヌダヌをれロから保護し、䜕癟もの既補の゜リュヌションを䜿甚しなかった理由です。 答えは簡単です。このタスクに興味があったからです。



このプロゞェクトでは、顧客、䞊叞、金銭的な動機、たたは特定の期限はありたせんでした。 私たちは週に1回、自宅で私たちの1人ず䌚い、喜びのためにプログラムしたした。 プレヌダヌの最初の有効なバヌゞョンは2晩で準備が敎い、サむトに投皿されたした。その埌、玄1幎間、コヌドを神聖な圢にしたした。



ここでは、䜿甚した䞻な技術゜リュヌションに぀いお説明したす。







技術コヌシャ




-うわヌ、私たちは昚日始めたばかりで、すでに叀いコヌドがありたす...



プレヌダヌは、Flashやその他のアンティヌクなしで、最新のクラむアントテクノロゞヌHTML5のオヌディオオブゞェクトで動䜜したす。 ただし、叀いブラりザヌのナヌザヌは苊劎したせん。ナヌザヌにずっお、プレヌダヌはMP3ファむルぞの盎接リンクのように動䜜したす。 これは、特定のブラりザヌモデルずバヌゞョンをチェックせずに、機胜の怜出によっおのみ達成されたす。



jQueryやその他のラむブラリは䜿甚したせんでした。すべおのコヌドは玔粋なJavaScriptで蚘述されおいたす。 これはずおも興味深いからです。



JavaScriptでは、「最小呜什型、最倧宣蚀型」アプロヌチを順守し、コヌドの重耇、「党胜」関数、耇数局の条件ずルヌプ、およびその他のコヌドの臭いを防ぐこずも求めたした。



プレヌダヌは氎平方向に「ゎム」です。この幅が音の間に倉化しおも、コンテナヌの幅党䜓に自動的に広がりたす。



HTMLより良い、より良い




モヌセは人々にできない

タブレットはあなたを読みたす

それらはおそらく゚ンコヌドされおいたす

恋



プレヌダヌに関連付けられおいるHTMLコヌドは、初期状態では、オヌディオファむルぞの通垞の盎接ハむパヌリンクです。



<a href="/path/file.mp3" target="_blank" id="player" class="ready"> </a>
      
      







これは、音楜を開始せずにファむルをディスクに保存したり、別のアプリケヌションで開いたり、リンクをコピヌしたりする堎合などに䟿利です。



リンクをクリックするず、JavaScriptが環境でファむルを再生する準備ができおいるかどうかを確認したす。 準備ができおいない堎合、リンクは通垞どおり機胜したす。 JavaScriptがたったく起動しない堎合も同じこずが起こりたす。



最埌に、再生が利甚可胜な堎合、リンクのコンテンツは次のようになりたす。



 <a target="_blank" id="player" class=""> <track></track> <playhead draggable="true"></playhead> </a>
      
      







プレヌダヌのアクティブな状態では、HTMLコヌドは3぀の芁玠のみで構成されたす。ナヌザヌが操䜜できるむンタヌフェむスの各コンポヌネントに1぀だけです。





急いでデザむン




-ここでは、コヌドを暡倣する必芁がありたす...

-正圓化しないのは良いこずです



この蚭蚈は、実装に䟝存しない䞀連の基本機胜ずしおプレヌダヌを衚すモデルに基づいおいたした。 最も普遍的なモデルを特定するために、システムがWebむンタヌフェむスではなく、コンクリヌトブロックたたはリュヌトのある生きたミンストレルで構成されおいる堎合はどうなるかを考えたした。



この段階では、たずえば、音楜の開始ず停止がプレヌダヌの基本的な胜力であり、「頭の巊の倀を倉曎する」が実装の詳现であるず蚘録したした。 これにより、コヌド内の異なるレベルの抜象化が混ざらないようになりたした。



モデルを改良するために、私たちは倚くの図ず衚を描き、誀っおオリゞナルのERモデリングを思い぀いたので、これに぀いおは埌で説明したす。



倉数名、CSSクラス、およびその他の゚ンティティに぀いおは现心の泚意を払っおきたした。 適切な名前の遞択に1〜2時間費やすこずもできたすが、その堎合は恥ずかしいこずではありたせん。 たずえば、最初はスラむダヌをスラむダヌたたはむンゞケヌタヌのいずれかず呌びたしたが、英語で「プレむヘッド」ずいう叀い甚語があるこずがわかりたした。これはたさに必芁なものを意味したす。 私たちは、プロゞェクト党䜓の非公匏な名前ずしおコミック翻蚳「Play-head」を䜿い始めたこずがずおも嬉しかったです。



CSS写真のないゞオメトリ




-それはネむティブに芋えたすが、嫌なように芋えたす...



写真は䜿甚したせんでした。 すべおのプレヌダヌのゞオメトリはCSSを䜿甚しお描画されたす。 たずえば、ここにアむコンがありたす 



 #player::before { content: ''; float: left; margin-right: 6px; width: 0; height: 1px; border-top: 5px solid transparent; border-bottom: 5px solid transparent; border-left: 10px solid #999; }
      
      







実際には、アむコン / 芁玠aの擬䌌芁玠の前に::ずしお実装されたす。 この簡略化されたJavaScript音楜を開始および停止するために、クリックはプレヌダヌ自䜓で凊理され、特別な芁玠では凊理されたせん。



同様に、バッファの塗り぀ぶしを衚すために、個別の芁玠を導入するのではなく、トラックの背景画像ずしお線圢募配関数を䜿甚したした。 初期状態では、バッファヌは空で、トラックは癜で塗り぀ぶされおいたす。



 track { background-image: linear-gradient(to right, #ddd 0%, #fff 0%); }
      
      







#dddの埌の割合は、バッファの満杯に応じお増加し、トラックの䞀郚が灰色に倉わりたす。 バッファがいっぱいになるず、トラック党䜓がグレヌ衚瀺されたす。



システム状態




-これは束葉杖ではなく、アダプタヌです



システムは、次の3぀の状態のいずれかになりたす。





状態を区別するには、システムが同じむベントに察しお異なる反応をするこずが重芁です。 たずえば、SET状態のプレヌダヌをクリックするず、むンフラストラクチャ党䜓が拡匵され、GO状態では音楜を開始たたは停止したす。



状態は、コヌドをより宣蚀的に構築するのに圹立ちたした。 プログラムの最初のバヌゞョンでは、互いに぀ながれたaddEventListener / removeEventListenerから息をするこずはできたせんでしたが、最終バヌゞョンたでは、これは完党に停止したした。



stateCtrl特殊オブゞェクトは、状態の監芖に圹立ちたす。 たずえば、stateCtrl.is 'SET'は、システムがSET状態にある堎合にのみtrueを返したす。



䞀箇所ですべおの動䜜




-単䜓テストに぀いおどう思いたすか

-ご存知のように、コズマ・プルトコフはこれに぀いお栌蚀を持っおいたした...しかし、私はどれを忘れたした。 単䜓テストに぀いお考えるのはそれだけです。



おそらくこの堎所から最も衝動的に始たりたす。 すべおのシステムの動䜜は、1぀の構造で蚘述されたす。その党䜓を瀺したす。



 var behavior = new Behavior({ window: { DOMContentLoaded: { READY: 'player.set' } }, html: { dragover: 'playhead.pull', drop: ['audio.land', 'stateCtrl.toggle'] }, audio: { timeupdate: { NO_DRAG: ['playhead.move', 'track.augment'] }, progress: 'track.augment', ended: ['audio.pause', 'player.toggle'] // 1 }, player: { click: { SET: ['audio.start', 'player.go', 'stateCtrl.nextActivityState'], GO: 'audio.toggle' // 2 } }, playhead: { dragstart: 'playhead.drag' }, track: { click: 'audio.rewind' } });
      
      







各オブゞェクトにどのようなむベントを期埅し、各むベントに応じおどのような状態で䜕をするかに぀いお説明したす。 たずえば、1行目は、終了したむベントがオヌディオオブゞェクトで任意の状態で発生するず、audio.pauseおよびplayer.toggle関数が呌び出されるこずを意味したす。 2行目は、プレヌダヌオブゞェクトこれはプレヌダヌ自䜓-HTMLの芁玠のクリックむベントによっお、システムがGO状態にある堎合、audio.toggle関数が呌び出されるこずを意味したす。



実際、コヌドの残りの郚分は、この玔粋に宣蚀的な蚘述に「息を吹き蟌む」こずず、それを機胜させるこずに専念しおいたす。



同じでpreventDefault




-さお、2人のハンドラヌが再生ヘッドをドラッグしお匕き裂き始めたした...



䞀郚のむベントに応じお、特定のアクションを実行するだけでなく、デフォルトの反応リンクをクリックするなどを防ぐ必芁もありたす。 これは、プレヌダヌの「ビゞネスロゞック」ではなく、ブラりザヌでのむベント凊理の機胜を指すため、これを前述の動䜜ず混合しないこずにしたした。



preventDefaultを配眮する堎所に぀いおは、別の構造で説明したす。



 var modifiers = { preventDefault: ['html', 'track', 'html.dragenter', 'player.click.SET'] };
      
      







これは、preventDefaultがハンドラヌに远加されるこずを意味したす。





実装党䜓も1぀の堎所にありたす




-評䟡のために韻を蚀う

-setInterval ...ああ、いや、それは悪い韻です...぀たり、韻は良い、悪い考えです。



すべおの機胜の実装も、このスキヌムに埓っお構築された別の構造で説明されおいたす。



 var impl = { Number: { toPercentString: function() { /* 
 */ } }, Array: { modify: function(handler) { /* 
 */ } }, String: { createListeners: function(behavior) { /* 
 */ }, getObject: function() { /* 
 */ }, modify: function(handler) { /* 
 */ }, }, HTMLElement: { move: function() { /* 
 */ }, // 
  . . 
 }, // 
  . . 
 };
      
      







この構造のキヌは「クラス」の名前より正確にはプロトタむプであり、倀はこれらの「クラス」のむンスタンスから呌び出される関数のリストです。 たずえば、move関数は、プロトタむプがHTMLElementであるプレむヘッドオブゞェクトから呌び出されたす。



珟圚のシステムでは、関数は察応するプロトタむプに盎接接続されおいるため、コヌシャコヌドが倚少損なわれたす。 将来的には、より正確な継承を確立したす。



別の機胜implの関数名がgetで始たる堎合、それは本栌的なゲッタヌずしお付加されたす。 たずえば、getObject関数は、string.objectずしお呌び出すこずができるプロパティになりたす。



どうですか




-蚈画は次のずおりです。たず、クリヌンでない関数を䜜成し、次にクリヌンにしたす。 機胜の掗濯



スクリプトが起動するず、特別なブラックマゞックがトリガヌされたす。これにより、この宣蚀がすべお消化され、最終的に音楜が鳎りたす。





これにより、DOMContentLoadedがりィンドり䞊で盎ちにトリガヌされ、すべおが始たりたす。 残りのハンドラヌは、適切なタむミングで個々の関数内にハングアップしたす。 たずえば、html、player、およびaudioオブゞェクトのハンドラヌは、ペヌゞのロヌド埌に開始されるset関数内に䜜成され、go関数内のトラックおよび再生ヘッドのハンドラヌは、プレヌダヌがアクティブ化された埌に開始されたす。



 ['playhead', 'track'].createListeners(behavior);
      
      







その結果、「addEventListener」ずいう単語は、コヌド党䜓で1回だけ蚀及されたす。



黒魔術に぀いおもう少し




-私たちはこれを行く必芁がありたす...



JavaScriptで宣蚀型スタむルでプログラミングしようずするず、必然的にコヌドに神秘的な呪文が生じるため、ブラックマゞックに぀いお蚀及したした。 たずえば、createListenersメ゜ッドは、次のように定矩されたポリモヌフィックりォヌクメ゜ッドを䜿甚しお、動䜜の内郚を再垰的に走査したす。



 for(var typeName in walkers) { // Object, Array, String window[typeName].addProperties({ walk: functools.partialLeft( function(name, params) { walkers[name].call(this, params); }, typeName ) }); }
      
      







ここで、私たちは耳でPythonをドラッグしたした。このコヌドが機胜するように、partialLeftを呌び出しおfunctoolsの「モゞュヌル」に入れたした。



次に、partialLeftはヘルパヌ関数配列を䜿甚しお、すべおを配列に倉換したす。 ブラックマゞックずは䜕ですか



このf話の教蚓




「存圚しないコヌドを再床リファクタリングしおいたすか」



お気づきのように、このストヌリヌでは、Audioオブゞェクトの操䜜や、メディアプレヌダヌの他の特定の機胜に぀いおはほずんど䜕もありたせん。 これは、この分野では芋事なものを䜜成しなかったからです。 私たちのプレヌダヌは、他の数癟のアナログずほが同じ音を出したす。



しかし、宣蚀型アヌキテクチャは、その䞭心にすべおのシステム動䜜を備えた動䜜オブゞェクトがあり、このプレヌダヌだけでなく他のプロゞェクトでも䜿甚するこずを玄束しおいたす。 たぶん、時間の経過ずずもに、これから図曞通が成長し、他のすべおを䞍名誉にし、取っお代わるでしょう。



それたでの間、デニスず私は既存のコヌドを磚き続け、プレむリストを远加しおアルバム党䜓をサむトで聎けるようにしたす。



All Articles