再生履歴の日付表示について

再生履歴に表示される「x日前」という日付表示について、言い訳っぽい話。

再生履歴の一覧は、日付毎に見出しがつきます。2日前なら「2日前」、1週間以上前なら「x月x日」という日付で表現されます。
ところが、本日再生分と1日前再生分のタイトルは、恐らくAndroidのバージョンによって変わってきます。 どこから変わってくるのかよく分かりませんが、Android 4.4 (CyanogenMod 11)では、「今日」「昨日」と表示され、Android 4.1 (SOL22)、Android 4.0.4 (ISW13HT) の場合は、「0日前」「1日前」と表示されてしまいます。
これは、DateUtilsのgetRelativeTimeSpanStringメソッドの挙動です。「今日」「昨日」と表示されるのが、本来正しい表示かと思います。とりあえず、バージョンや端末によって違ってくるかと思いますが、「0日前」と表示されたところで動作に影響があるわけではないので、この辺の表示はとりあえずこのままの方向で。

SOL22 (Android 4.1)

CyanogenMod11 (Android 4.4)

Medoly Ver. 1.6.0, 1.6.1, 1.6.2

Ver. 1.6.0 変更履歴

2014-08-07 Ver. 1.6.0
– ロック画面コントロール追加 (設定でON/OFF切換)
– 通知バーからコントロール可能に変更
– 再生履歴のディスク番号が正しく表示されない問題修正
– 再生履歴が正しくチェックされない問題修正

ロック画面コントロール追加 (設定でON/OFF切換)

ロック画面へのコントロールを追加しまいた。Android 4.0以降の対応となります。
設定画面でON/OFFできます。ロック画面コントロールのデザインはこちらからは制御できず、端末の機能に依存します。「ロック画面をこうしたい」という場合は、端末メーカーまでご要望をお伝え頂ければ…。

通知バーからコントロール可能に変更

シンプルな通知バーだったものを、コントロール可能な通知バーに変更しました。Android 4.0以降の対応となります。

再生履歴のディスク番号が正しく表示されない問題修正

再生履歴のトラック番号が正しく表示されなかった問題を修正しました。昔からある潜在的な問題でした。

再生履歴が正しくチェックされない問題修正

チェック項目の表示処理が間違っていました。

Ver. 1.6.1 変更履歴

2014-08-07 Ver. 1.6.1
– 通知バーで文字が重なる問題修正

通知バーで文字が重なる問題修正

通知バーのコントロールを作成した際、レイアウトの問題で文字が長いと文字同士が重なる問題があったため、修正しました。

Ver. 1.6.2 変更履歴

2014-08-07 Ver. 1.6.2
– 歌詞のスクロール処理を修正

歌詞のスクロール処理を修正

歌詞が正しくスクロールされない問題を修正しました。

Medoly Ver. 1.5.5, 1.5.6, 1.5.7, 1.5.8

Ver. 1.5.5 変更履歴

2014-08-01 Ver. 1.5.5
– メイン画面拡大機能追加
– ヘッドセットボタンの複数回押しによるメディア切換機能追加
– 歌詞の行末尾にタイムタグがある場合のアクティブ化処理を変更
– 共有されたAndroid DB未登録メディアを再生キュー登録可能に変更
– アルバムアート背景を白に設定できなくなっていた問題修正
– 再生履歴のスクロールで落ちる場合がある問題修正
– 再生履歴にトラック番号表示するよう修正
– サムネイル読込み処理修正
– アプリ起動時に再生キューが現在メディアにスクロールしない問題修正

メイン画面拡大機能追加

表示タブのメニューに「メイン画面の拡大」項目を追加しました。メイン画面下部のコントロール領域と、Androidの通知バーを非表示にします。とにかく表示領域だけを大きく取りたい用途向けです。どのような用途を想定しているかと言うと、歌詞やアルバムアートだけを表示させたり、HDMI等で外部出力した際に、操作部分を隠して表示部分だけを大きくしたい場合等です。
まぁ、思いつきで追加した機能なので、もう少し色々検討するかもしれません。こういう事もできますよ、という実験みたいなものなので。

ヘッドセットボタンの複数回押しによるメディア切換機能追加

今までもヘッドセットのボタンを押すと、再生・停止を切り替えることができましたが、2回押しで次の曲、3回押しで前の曲に切り替える機能を追加しました。
iPhoneのように、「2回押し+押しっぱなし」で早送り、「3回押し+押しっぱなし」で巻き戻し…という機能はありません。ボタンを押しっぱなしにするとAndroid標準の音声検索が起動してしまうため、実装することができませんでした。キーを横取りできるかもしれませんが、そこまで頑張る必要はないかなぁ…と。
この機能を利用するには、設定画面よりONにします。複数回押しを検出させるために、ボタンを押した際の反応を少し遅らせていますので、使わない場合はOFFの方が良いでしょう。

歌詞の行末尾にタイムタグがある場合のアクティブ化処理を変更

歌詞の表示について。細かい話かもしれないけど、こだわる人には多分重要な変更。
タイムタグが設定された同期歌詞において、1行の末尾にタイムタグがある場合。例えば以下のような感じ。

[00:00:00]
[00:10:00]あ[00:11:00]い[00:12:00]う[00:13:00]え[00:14:00]お[00:15:00]
[00:20:00]

この場合、今までは開始10秒で2行目の歌詞がアクティブ状態になり、20秒で3行目がアクティブになるまで、2行目がアクティブ状態となっていました。
今回の変更で、 15秒で2行目が非アクティブ状態となり、3行目がアクティブになる20秒まではアクティブ行が存在しない状態になります。
これにより、ボーカルの切れ目等を表現できるようになります。

 

共有されたAndroid DB未登録メディアを再生キュー登録可能に変更

今までは、Android DBに登録された曲しか再生することができなかったのですが、未登録曲も再生できるようになりました。未登録曲は、他のアプリから本アプリに対して「共有」させることで再生キューに登録され、再生できるようになります。ただし、この曲はプレイリストに登録することはできません。Androidのプレイリスト仕様上不可能です。一応、独自プレイリストも作れるようにしてはあるので、時間がある時にでも実装してみます。

アルバムアート背景を白に設定できなくなっていた問題修正

アルバムアートの背景は白・黒で切り替えられるようになっていますが、白くできなくなっていたので修正しました。ボタンの背景画像を弄った際に誤って修正していました。

再生履歴のスクロールで落ちる場合がある問題修正

再生履歴のリストについての問題です。サムネイルの取得に失敗していました。

再生履歴にトラック番号表示するよう修正

再生履歴のアルバム名にトラック番号を表示するようにしました。

サムネイル読込み処理修正

サムネイル読込み処理を修正しました…が、これは後のバージョンで再度修正されています。

アプリ起動時に再生キューが現在メディアにスクロールしない問題修正

アプリ起動時に、再生キューがスクロールしない問題を修正…したつもりが、あまり修正されていません。調査中です。

Ver. 1.5.6 変更履歴

2014-08-02 Ver. 1.5.6
– サムネイル表示にアニメーション追加
– 再生履歴の表示が遅くなる問題修正
– 余分なスクロール処理を省略するように修正

サムネイル表示にアニメーション追加

サムネイル表示時に、短時間のフェードインアニメーションを追加しています。
サムネイル表示の際に、キャッシュから取得する場合と、アルバムアートから取得する場合があります。この2パターンの取得は当然表示までの時間差があります。スクロールしていった場合、キャッシュ画像は画面に入った段階で表示済みになりますが、画像は一旦サムネイルに落とし込むため、表示に時間がかかります。そのため、表示タイミングに差が生まれて違和感がありました。そのため、一律で短時間のフェードインアニメーションを追加することで差異を少なくして、違和感を緩和させています。
…この違和感は、私だけかもしれません。

再生履歴の表示が遅くなる問題修正

再生履歴ダイアログにリストを表示した場合、サムネイルが1枚読込まれる度に画面全体のレイアウトが再読込され、全ての表示項目にgetViewメソッドが呼ばれ、表示が非常に遅くなっていました。これはリストの高さを固定していないことで発生する事象です。

参考: [Android] ListViewのlayoutをwrap_contentで指定しない | Developers.IO

通常のActivity上で行う分には発生しにくいと思いますが、ダイアログに表示した場合、高さにmatch_parentを指定するだけではダメで、ダイアログに表示するビューの高さを固定しないと割と簡単に発生します。 これの原因を突き止めるのは割と苦労しました…。

余分なスクロール処理を省略するように修正

スクロール処理が複数回発生していたポイントがあるので、それを修正しました。

Ver. 1.5.7 変更履歴

2014-08-03 Ver. 1.5.7
– サムネイルの更新処理を修正
– 再生履歴ダイアログの末尾が見えない問題修正
– シャッフル再生において正しくシャッフルされていなかった問題修正

サムネイルの更新処理を修正

サムネイルの取得・更新処理を修正しました。バグ修正です。

再生履歴ダイアログの末尾が見えない問題修正

Ver.1.5.6でダイアログの高さを固定しましたが、固定した高さがおかしく、最後の行が途中で切れてしまう問題があったので、固定の高さを修正しました。

シャッフル再生において正しくシャッフルされていなかった問題修正

シャッフルの処理が、ずっと処理を間違ってました。再生キューをクリアせず、曲の追加・削除を繰り返すとシャッフルがどんどんズレていくという問題があったため、修正しました。

Ver. 1.5.8 変更履歴

2014-08-03 Ver. 1.5.8

– リストのサムネイル読込み時に落ちる問題修正

リストのサムネイル読込み時に落ちる問題修正

タイトルの通り。バグ修正です。

Medoly ver. 1.5.0

1.5.0という、ちょっと節目っぽいバージョンだったので、この時は結構更新を頑張りました。

変更内容

2014-07-19 Ver. 1.5.0
– 再生履歴機能追加
– タブメニューのデザイン変更
– 再生順メニューのデザイン及び挙動変更
– 通常再生/シャッフル再生切換時の処理を高速化
– タブタップ時の色替え追加
– ハードウェアキーの早送り・巻き戻し対応
– サムネイルの非表示設定追加
– 設定項目一部修正
– 内蔵アルバムアートが共有できなくなっていた問題修正
– キューを削除した場合に落ちる場合がある問題修正
– 歌詞の先頭空白行でスクロールしない問題修正
– ボタン背景やアイコンデザインをいくつか修正
– 内部の処理をいくつか修正


再生履歴機能追加

再生キュータブのメニューから呼び出します。再生済み曲のリストを表示し、再生順と逆順で表示されます。
一瞬でも再生すると追加されます。再生履歴上に存在する曲を再生した場合、過去の履歴は削除され、新たに先頭に追加されます。 
再生履歴の曲を選択し、「開く」ボタンを押すと再生キューに追加します。再生キューに曲が存在する場合は、再生キューの末尾に追加するか、再生キューをクリアして新たに追加するかの確認ダイアログが表示されます。(プレイリストを開く場合と同様)
設定画面で、保存履歴数を0~999で変更を行うことができます。多くすると、リストが開くまで時間がかかるかもしれません。 0にすると、タブメニューの項目自体表示されなくなります。
とりあえず簡易的なもので、それほど高機能なものではありません。

タブメニューのデザイン変更

タブのメニューを丸みを帯びた吹き出しのようなデザインに変更しました。機能的な変更はありません。今まではAndroid標準の「PopupMenu」というクラスを利用していたのですが、これは表示位置やデザインの調整ができないため、メニューが下に表示されてしまう等の問題がありました。これを「PopupWindow」クラスに変更することで、表示位置を調整できるようにすると共に、メニューを自由にデザインできるように修正しました。 (逆に自分で色々作り込まないといけなくなるので、ちょっと面倒でした。)

再生順メニューのデザイン及び挙動変更

再生順メニューも、タブメニュー同様に変更しました。また、ここのメニューはチェックを切り替えてもメニューが閉じません。閉じるには、枠外をタップします。この挙動の変更は、再生順「通常」・再生完了「停止」から、再生順「単体」・再生完了「リピート」に切り替えたいと思う場合、複数のチェック切換が必要になるため、一々ポップアップメニューが閉じられてしまうと作業が繁雑になってしまうためです。

通常再生/シャッフル再生切換時の処理を高速化

本アプリは、曲の再生順を全て自前で制御しています。
そのため、再生順の「通常」と「シャッフル」を切り替えるとDB上の再生キューの再生シーケンス番号を更新する処理が走ります。 この時の処理を変更(無駄な処理を排除)して、更新を高速化しました。
再生キューに多数の曲が登録されていると、流石に処理が一瞬止まってしまうのはご了承ください。(もしかしたら改善策はあるかもしれません。)

タブタップ時の色替え追加

メイン画面のタブをタップした際に、タブをハイライトするようにしました。操作に対する視覚的なフィードバックを与えるという、UIの改善です。

ハードウェアキーの早送り・巻き戻し対応

早送り・巻き戻しのハードウェアキー(リモコンのキー)に対応しました。
…対応してるはずです。「はず」というのは、早送り・巻き戻しのボタンを持ってるリモコンを持ち合わせていないので、テストしていないためです。 早送り・巻き戻しを押した時と同様の処理をしているので、動くはずです。…理論上は。

サムネイルの非表示設定追加

サムネイルを非表示に出来るようにしました。設定画面から変更します。設定を変更すると、再生キュー、検索画面、再生履歴のサムネイルを非表示にします。非表示にすると、スクロールが高速になります。もちろん、サムネイルが表示されなくなるので、曲を探す際の視認性は悪くなります。何はともあれ動作速度が欲しい人向け。

設定項目一部修正

設定項目を一部見直しました。
細かい内容は忘れました…。

内蔵アルバムアートが共有できなくなっていた問題修正

プロパティタブから内蔵アルバムアートを「共有」できるようにしたつもりできたが、出来ていませんでした。バグ修正です。

キューを削除した場合に落ちる場合がある問題修正

バグ修正です。

歌詞の先頭空白行でスクロールしない問題修正

同期歌詞で歌詞の先頭が空白だった場合、初期位置が先頭に来ない問題があったため、修正しました。

ボタン背景やアイコンデザインをいくつか修正

歌詞・アルバムアート表示タブに表示されるボタンの背景の修正です。
このボタンは元々曰く付きで、Androidでデフォルトで用意されている「Holo」というテーマ標準のボタンは、透明度が高すぎて単純に配置しただけでは背景のアルバムアート画像が透けすぎてボタンが見えにくいという難点がありました。これを変更する方法が分からず、ボタンの後ろにもう1枚オブジェクトを重ねて不透明化するという荒技をやっていました。
…が、いい加減何とかしようと思って調べたもののやっぱり分からない…というか、多分設定から変更するような方法は無いと思うので、 ボタン背景画像の差し替えを行いました。
以下のサイトから、Holoと同様のイメージを生成できます。

内部の処理をいくつか修正

細々したバグや挙動の修正です。
何やったかは忘れましたが、変更履歴に書いたので、多分何かやったのでしょう。

Medoly ver. 1.4.7

ちょっと時間が出来たので、凄い久しぶりに過去の更新内容詳細についてまとめていきたいと思います…。

変更内容

2014-07-01 Ver. 1.4.7
– ウィジェットに再生アイコンを表示するよう変更
– 画像読込み処理修正
– 検索画面の状態保存処理一部修正
– 再生中の曲情報が更新されない場合がある問題修正

ウィジェットに再生アイコンを表示するよう変更

これ。

画像読込み処理修正

画像の読込み処理を直して、大きな画像を読込んだ時に余分なメモリを消費しないようにしました。

検索画面の状態保存処理一部修正

検索画面を再表示した際に、閉じる前の状態に戻す処理が一部おかしかったので修正。

再生中の曲情報が更新されない場合がある問題修正

タイトルの通り。バグの修正です。

Medoly ver.1.3.5 ~ 1.4.6

物凄い勢いで更新をサボってるので、最近の大雑把に変更点を…。

再生キュー処理の高速化

表面的には変わってませんが、再生キューの動きを高速化しています。いくつか変更を施していますが、主に以下のような変更になります。以前は、再生キューに5000曲ぐらい入れるとまったく使い物になりませんでしたが、これで大分マシになりました。(そんな使い方をする人がいるかどうかはともかく。)

  • 同期的に読込んでいたサムネイル画像を非同期で読込むように変更しました。そのため、スクロールの引っかかりが少なくなっています。
    またそれに伴い、以前は再生キュー登録時にサムネイルの縮小率の計算を行っていましたが、それを削除しました。そのため、再生キューへの登録が大幅に高速化されています。端末のスペックにもよりますが、100曲程度の登録なら一瞬で終わります。
  • タップした再生キュー項目のインデックスを取得する処理を見直してます。これに伴い、タップしてから再生開始までの間隔が短くなっています。
  • 再生キューテーブルの余計なデータを削除し、テーブルを少し修正しました。また、インデックスを追加しました。これにともない、読込処理が少し高速化されています。

早送り・巻き戻し追加

今さらながら、早送り・巻き戻しボタンを追加。設定で表示・非表示可能。
1回のタッチで設定秒数だけシーク、押しっぱなしで早送り・巻き戻しという動作になります。早送り・巻き戻しとは言っても、何かそれっぽく動かしているだけで、実際のところは断続的なシークの繰り返しで擬似的に早送り・巻き戻しをやってるだけです。200ms毎にシークし、押す時間が長いと段々シーク秒数が増えていくような動作になります。一般的な動作がよく分からないので、感覚的に適当に実装しました。

再生キューの削除をダイアログ方式に変更

従来メニューにあった再生キューの削除アクションを、ダイアログ方式にしました。削除までに、メニュー表示→ダイアログ表示→コマンド選択の3アクションになったので、やや煩わしくなったかもしれません。その代わり、いくつかの削除方法を追加しました。また、再生完了メディアの自動削除、エラーメディアの自動削除オプションを追加しています(設定画面にあったものを移動)。

プロパティタブの機能追加

  • プロパティリストにファイル共有処理を追加しています。音楽ファイル、アルバムアート、歌詞ファイルの「共有」ができます。また、歌詞についてはクリップボードへのコピーも可能となっています。 プロパティをタップした時に表示されるメニューから実行できます。
  • プロパティの一部項目から直接Web検索可能としました。
  • 上記アクションを追加したため、分かりやすくするためにプロパティリストに実行可能なアクションを示すアイコンを追加しました。

検索画面のソートアイコン追加

検索画面のソートアイコンを追加しました。(従来、設定画面にあったものと同等です。)
このアイコンをタップすることで、検索内容のソート順を変更できます。ただし、「ストレージ」タブの内容は変化しません。

検索画面の長押しメニュー追加

検索リスト項目を長押した時にメニューを表示し、再生キュー登録の細かい動作を指定することができます。とりあえず、簡単な動作を設定することができます。 このダイアログの設定は、このダイアログ以外の再生キュー追加動作に影響を与えません。

検索画面のストレージ表示状態で、ホームアイコン長押しによるホームパス設定追加

検索画面のストレージタブでホームタブを長押しすると、現在タブに表示されているパスをホームパスに設定できます。ただのオマケ機能です。

英語対応

…とりあえず英語対応させてみましたが、多分酷い英語です。とりあえずツッコミ待ちです。
この変更を行った後に英語でメールを頂いたので、 多少意味があったような気がします。

その他

非同期歌詞のスクロール位置を保持するようにするなど、細かい変更はいくつもありますが、とりあえず省略。多分これからも細かい修正は色々行っていきたいと思います。

今回、設定画面の項目をいくつかをメイン画面や検索画面に移動させています。これは、個人的に設定項目は可能な限り設定画面に置かず、操作する対象の近くに置きたいという方針のためです。今後もこうした変更はちょくちょく加えていくと思います。

Medoly ver.1.3.1 & 1.3.2 & 1.3.3

Medoly

ちょっと変更。

Ver. 1.3.1

 

検索画面の検索条件やスクロール位置をなるべく保持させる処理を追加

アプリの再起動時や検索画面に戻った時、検索条件を切り替えた時に、検索条件やスクロール位置を可能な限り保持するようにしました。
「可能な限り」というのは、曲が追加されたり変更された際に、どうしても位置を保持することが難しくなるため、そういった場合はズレが生じてきます。
保持させるタイミングや復帰させるタイミングがかなり入り組んでいて、色々面倒臭い処理になっているため、もしかしたらリセットされる場合があるかもしれません…。

再生キューのトラック・年が隠れないようにレイアウトを変更

再生キューには、アルバム名にディスク/トラック番号、アーティスト名に年が併記されていますが、アルバム名・アーティスト名が長い場合は ディスク/トラック番号・年が枠外に隠れてしまっていました。そんなわけで、 ディスク/トラック番号・年が隠れないようにレイアウトを修正しました。(アルバム名・アーティスト名のみが省略されます。)

Ver. 1.3.2

 

検索画面のアクティビティを再表示させた際、タイトル表示になってしまう問題を修正

Ver.1.3.1の修正ミスです。アクティビティ再表示が上手くいかないバグがありました。

Ver. 1.3.3

 

歌詞の多段表示対応

同じ時間で2行以上の歌詞がアクティブの場合、表示上も同時にアクティブにするようにしました。
2行以上が同時にアクティブになる場合は、行内にタイムタグが設定されている場合です。
例えば、以下のようなKRAファイルがあるとします。
[00:01:00] あ[00:02:00]い[00:03:00]う[00:04:00]え[00:05:00]お[00:06:00]
[00:04:00] か[00:05:00]き[00:06:00]く[00:07:00]け[00:08:00]こ[00:09:00]
上記の場合、再生開始から4~6秒の間は、1行目と2行目が両方アクティブな状態です。
文字単位で見ると、5秒時点では、1行目は「あ、い、う、え、お」が、2行目は「か、き」がアクティブな状態になります。
これは例えば、曲の中でメインボーカルとコーラスが被っている部分を表現する、といった利用を想定しています。

KRA -> LRC, LRC -> KRA 変換スクリプト

歌詞定義フォーマットの、LRCファイルKRAファイル(タイムタグフォーマット)の変換用WSHスクリプト(JScript)を作成したので公開。KRA -> LRC変換と、LRC -> KRA変換の2つ。WSHスクリプトなので、Windows専用。

[使い方]

  1. KRA -> LRC変換スクリプトの場合はKRAファイル(*.kra)をドラッグ&ドロップします。
    LRC -> KRA変換スクリプトの場合はLRCファイル(*.lrc)をドラッグ&ドロップします。
  2. KRAファイルの場合は、同じフォルダ上にLRCファイル(KRAファイル名.lrc)が作成されます。
    LRCファイルの場合は、同様にKRAファイルが作成されます。

[注意事項]

  • 埋め込まれているタグのうち、 タイトル、アーティスト、アルバム、作詞者、曲の長さ、ファイル作成者以外の情報は削除されます。
  • UnicodeのBOMが不要な場合は、コメントアウトしてある「// delete BOM」の辺りをコメント解除すると、BOMが削除されます。
  • あんまり真面目にテストしてないので、きちんと動かない場合があるかもしれません。 
  • とりあえず拡張子固定なので、拡張子lrcでKRAフォーマットにしている人や、拡張子txtでフォーマット定義してる人は知ったこっちゃないです。

KRA -> LRC変換WSHスクリプト

/*==== 設定 ==================================================================*/

var inputExt = /.kra$/i;
var outputExt = ".lrc";
var inputEncoding = "_autodetect_all";
var outputEncoding = "UTF-8";

/*============================================================================*/



/*==== 定数 ==================================================================*/

// 保存データの種類
// StreamTypeEnum
// http://msdn.microsoft.com/ja-jp/library/cc389884.aspx
var adTypeBinary = 1; // バイナリ
var adTypeText   = 2; // テキスト

// 読み込み方法
// StreamReadEnum
// http://msdn.microsoft.com/ja-jp/library/cc389881.aspx
var adReadAll  = -1; // 全行
var adReadLine = -2; // 一行ごと

// 書き込み方法
// StreamWriteEnum
// http://msdn.microsoft.com/ja-jp/library/cc389886.aspx
var adWriteChar = 0; // 改行なし
var adWriteLine = 1; // 改行あり

// ファイルの保存方法
// SaveOptionsEnum 
// http://msdn.microsoft.com/ja-jp/library/cc389870.aspx
var adSaveCreateNotExist  = 1; // ない場合は新規作成
var adSaveCreateOverWrite = 2; // ある場合は上書き

/*============================================================================*/



var args = WScript.Arguments;
if (args.length == 0) {
 WScript.Echo("引数を指定するか、ファイルをドラッグ&ドロップしてください。");
 WScript.Quit();
}

for (var i = 0; i < args.length; i++) {
 var path = args(i);
 if (path.match(inputExt) != null) {
  writeLyrics(path);
 }
}

function writeLyrics(path) {
 var streamReader = new ActiveXObject("ADODB.Stream");
 var streamWriter = new ActiveXObject("ADODB.Stream");
 try {
  // read
  streamReader.Open();
  streamReader.Type = adTypeText;
  streamReader.Position = 0;
  streamReader.Charset = inputEncoding;
  streamReader.LoadFromFile( path );
  var text = streamReader.ReadText( adReadAll );
  if (text.charCodeAt(0) == 0xFEFF || text.charCodeAt(0) == 0xFFFE) {
   text = text.substr(1); // delete BOM
  }
  
  // replace
  text = text.replace(/(^s+)|(s+$)/g, "");
  text = text.replace(/(rn|n|r|^)[(dd:dd):(dd)]/g, "$1[$2.$3]");
  text = text.replace(/[(dd:dd):(dd)]/g, "<$1.$2>");
  
  text = text.replace(/@Title= *(.*?)(rn|n|r|$)/g, "[ti:$1]$2");
  text = text.replace(/@Artist= *(.*?)(rn|n|r|$)/g, "[ar:$1]$2");
  text = text.replace(/@Album= *(.*?)(rn|n|r|$)/g, "[al:$1]$2");
  text = text.replace(/@Lyricist= *(.*?)(rn|n|r|$)/g, "[au:$1]$2");
  text = text.replace(/@Length= *(.*?)(rn|n|r|$)/g, "[length:$1]$2");
  text = text.replace(/@TaggingBy= *(.*?)(rn|n|r|$)/g, "[by:$1]$2");
  
  var offset = text.match(/@Offset= *(.*?)(rn|n|r|$)/);
  if (offset && offset.length >= 2) {
   text = text.replace(/@Offset= *(.*?)(rn|n|r|$)/, "[offset:" + (offset[1] * -1) + "]$2");
  }
  
  text = text.replace(/(@.*=.*?)(rn|n|r|$)/g, "");
  
  // write
  streamWriter.Type = adTypeText;
  streamWriter.Charset = outputEncoding;
  streamWriter.Open();
  streamWriter.WriteText(text, adWriteChar);
  
  /*
  // delete BOM
  var bomSize = 0;
  if (outputEncoding == "UTF-8") {
   bomSize = 3;
  } else if (outputEncoding.indexOf("UTF-16") == 0) {
   bomSize = 2;
  }
  streamWriter.Position = 0;
  streamWriter.Type = adTypeBinary;
  streamWriter.Position = bomSize;
  var buffer =  streamWriter.Read();
  streamWriter.Position = 0;
  streamWriter.Write(buffer);
  streamWriter.SetEOS();
  */
  
  streamWriter.SaveToFile( path.replace(inputExt, outputExt), adSaveCreateOverWrite );
 } finally {
  if (streamReader != null) streamReader.Close();
  if (streamWriter != null) streamWriter.Close();
 }
}

LRC -> KRA変換WSHスクリプト

/*==== 設定 ==================================================================*/

var inputExt = /.lrc$/i;
var outputExt = ".kra";
var inputEncoding = "_autodetect_all";
var outputEncoding = "UTF-8";

/*============================================================================*/



/*==== 定数 ==================================================================*/

// 保存データの種類
// StreamTypeEnum
// http://msdn.microsoft.com/ja-jp/library/cc389884.aspx
var adTypeBinary = 1; // バイナリ
var adTypeText   = 2; // テキスト

// 読み込み方法
// StreamReadEnum
// http://msdn.microsoft.com/ja-jp/library/cc389881.aspx
var adReadAll  = -1; // 全行
var adReadLine = -2; // 一行ごと

// 書き込み方法
// StreamWriteEnum
// http://msdn.microsoft.com/ja-jp/library/cc389886.aspx
var adWriteChar = 0; // 改行なし
var adWriteLine = 1; // 改行あり

// ファイルの保存方法
// SaveOptionsEnum 
// http://msdn.microsoft.com/ja-jp/library/cc389870.aspx
var adSaveCreateNotExist  = 1; // ない場合は新規作成
var adSaveCreateOverWrite = 2; // ある場合は上書き

/*============================================================================*/



var args = WScript.Arguments;
if (args.length == 0) {
 WScript.Echo("引数を指定するか、ファイルをドラッグ&ドロップしてください。");
 WScript.Quit();
}

for (var i = 0; i < args.length; i++) {
 var path = args(i);
 if (path.match(inputExt) != null) {
  writeLyrics(path);
 }
}

function writeLyrics(path) {
 var streamReader = new ActiveXObject("ADODB.Stream");
 var streamWriter = new ActiveXObject("ADODB.Stream");
 try {
  // read
  streamReader.Open();
  streamReader.Type = adTypeText;
  streamReader.Position = 0;
  streamReader.Charset = inputEncoding;
  streamReader.LoadFromFile( path );
  var text = streamReader.ReadText( adReadAll );
  if (text.charCodeAt(0) == 0xFEFF || text.charCodeAt(0) == 0xFFFE) {
   text = text.substr(1); // delete BOM
  }
  
  // replace
  text = text.replace(/(^s+)|(s+$)/g, "");
  text = text.replace(/<(dd:dd).(dd)>/g, "[$1:$2]");
  text = text.replace(/[(dd:dd).(dd)]/g, "[$1:$2]");
  
  text = text.replace(/[ti: *(.*?)]/g, "@Title=$1");
  text = text.replace(/[ar: *(.*?)]/g, "@Artist=$1");
  text = text.replace(/[al: *(.*?)]/g, "@Album=$1");
  text = text.replace(/[au: *(.*?)]/g, "@Lyricist=$1");
  text = text.replace(/[length: *(.*?)]/g, "@Length=$1");
  text = text.replace(/[by: *(.*?)]/g, "@TaggingBy=$1");
  
  var offset = text.match(/[offset: *(.*?)]/);
  if (offset && offset.length >= 2) {
   text = text.replace(/[offset: *(.*?)]/, "@Offset=" + (offset[1] * -1));
  }
  
  text = text.replace(/[[^d].*?](r?n)?/, "");
  text = text + "rn@TimeRatio=1rn";
  
  // write
  streamWriter.Type = adTypeText;
  streamWriter.Charset = outputEncoding;
  streamWriter.Open();
  streamWriter.WriteText(text, adWriteChar);
  
  /*
  // delete BOM
  var bomSize = 0;
  if (outputEncoding == "UTF-8") {
   bomSize = 3;
  } else if (outputEncoding.indexOf("UTF-16") == 0) {
   bomSize = 2;
  }
  streamWriter.Position = 0;
  streamWriter.Type = adTypeBinary;
  streamWriter.Position = bomSize;
  var buffer =  streamWriter.Read();
  streamWriter.Position = 0;
  streamWriter.Write(buffer);
  streamWriter.SetEOS();
  */
  
  streamWriter.SaveToFile( path.replace(inputExt, outputExt), adSaveCreateOverWrite );
 } finally {
  if (streamReader != null) streamReader.Close();
  if (streamWriter != null) streamWriter.Close();
 }
}

備考

タイムタグ仕様書(一般者向け)で定義されたフォーマットを「KRAフォーマット」と書いてはいますが、「KRAフォーマット」と言う名称はどこにも定義されてないので、便宜上の名前です。
「タイムタグ」や「タイムタグ歌詞」という名称は、LRC等を含めた歌詞定義フォーマット全般で使われる名称なので、フォーマット名称としてはあまり適切ではないと思います。そんなわけで、主に利用される拡張子「KRA」に基づき「KRAフォーマット」と呼称しています。
LRCは「LRCフォーマット」として定義されているため、迷うことはないのですが…。

Medoly ver.1.3.0

Medoly

ちょっと変更が多いのでバージョンを飛ばします。

検索画面の検索結果にサムネイル表示

検索画面の検索結果にサムネイルを表示するようにしました。これは、自分の非同期処理の練習も兼ねてます。
画像の表示は非同期処理を行っていますので、表示されるまでにワンテンポ遅れます。非同期処理慣れしてないので、もしかしたらもっと上手く表示できるかもしれませんが、とりあえず今はこんなところで。あと、たまに画像が表示されない場合がありますが、対策がよく分かってないので、とりあえずこのままにします。そのうち何とかします。画像が表示されなくても大した問題はないので…。
ちなみに、同期処理で画像を表示させるとスクロールが非常に遅くなるので不可です。 

検索結果のソート順設定追加

検索結果のソート順序をしていできるようにしました。適用対象はタイトル表示のみです。ストレージの表示や各ディレクトリ(アーティストやアルバム等)の表示には適用されません。
「設定」画面から変更できます。変更後は全ての検索結果に適用されます。
デフォルトのソートは、従来通りタイトル昇順となっています。

ソートが大文字・小文字で区別されていた問題修正

上記のソート順指定処理で気付きましたが、再生キューのソート時に大文字・小文字で区別されていました。本来は大文字・小文字の区別をしないようにしたかったので、ソート仕様を変更します。
 

特定環境でジャンルが表示されないバグ修正

所持しているAndroid 3.2端末(HTC Flyer)で確認したところ、ジャンルの表示が正しく行われていなかったため、修正しました。
これがAndroid 3.2の仕様上の問題なのか、端末固有の問題なのか判断がつかないのですが…。

再生キューの長押しメニュー項目修正

前回削ったと思ってたら削られていなかった「再生」項目を削除しました。
…うっかり何かの拍子に元に戻してしまっていたようです。

文言一部修正

設定画面等の文言を一部修正しました。特に動作には関係ありません。

Medoly ver.1.2.6

Medoly

ちょっと思いついた事を実装。

再生キュー項目の長押しメニュー内容変更

再生キューの曲を長押しした際のメニューに「先頭に移動」と「末尾に移動」を追加。個人的に少しほしかったので。
代わりに「再生」を除外。再生は項目をタップすれば良いだけの話なので必要性は低いかな…と。

検索画面のジャンル表示高速化

検索のジャンル画面の表示を少し高速化しました。
Androidのメディアデータベースは、一つの曲が複数のジャンルを保持できる構成になっています。
各ジャンルに含まれている曲数を表示させるため、今までは各ジャンルそれぞれについて曲数をカウントするという事をやっていたため、やや処理が遅くなっていました。当然、ジャンル数が多いと時間がかかります。
最近ちょっと調べてみたところ、検索URIに「content://media/external/audio/genres/all/members」を用いることで、ジャンルIDと楽曲IDの紐付けを行うテーブルにアクセスできる事が分かりました(ちなみに「audio_genres_map_noid」というビューを参照している。実体は「audio_genres_map」テーブルの模様)。
これをジャンルIDでグループ化して、各グループのカウントを取得することで、ジャンルに含まれる曲数を素早く取得できるようになりました。
ちなみに、プレイリストも同様の処理を行うつもりでしたが、同様ののURIが見当たりません…。

検索画面のストレージ表示高速化

検索のストレージ画面の表示を高速化しました。
これも曲数カウント絡みで、従来はテーブルからカレントディレクトリに含まれるメディアを全て取得し、Javaのコードで項目をカウントするという力業で曲数を取得していました。当然、曲数が増えると処理に時間がかかるようになります。ディレクトリの階層が浅くなり、サブディレクトリに含まれるメディアが増えていくと、それに合わせて表示に時間がかかるようになります。
変更後は、SQLiteのGROUP BYとCOUNT関数を活用するようにしました。ただし、SQLiteは文字列長に合わせた柔軟なグループ化ができない(私が知らない)ので、以下のような感じのSQL処理を行うようにしました。
START = カレントディレクトリパスの文字列長
LENGTH = カレントディレクトリに含まれる最大ファイル・フォルダ文字列長
SELECT [ファイルパス], SUBSTR([ファイルパス], START, LENGTH) AS subpath, COUNT(subpath) as count
FROM [メディアテーブル]
WHERE [ファイルパス] LIKE カレントディレクトリ%
GROUP BY subpath

上記の内容は大雑把なイメージですが、とりあえずSQLで可能な範囲でざっくり纏めてしまうというのが基本的な方針です。もちろん、同一のサブフォルダが複数に分割される場合もありますが、細かいカウントの合計値はJavaの処理で行っています。
なお、この処理の特性上、カレントディレクトリに一つでも長い名前のファイル・フォルダが含まれていると処理の効率が悪くなります。
ただ、ほとんどの場合は表示が高速化されますし、上位ディレクトリに行く程表示が遅くなる従来の問題は無くなります。

検索画面のジャンル・プレイリストのカウントが正しく表示されない問題修正 

これは、カウントする処理が誤っていたバグです。

検索画面で一つでもチェックが入っていた場合に、タップ挙動をチェックの変更とする

検索画面で、リスト上に一つでもチェックが入っていた場合、項目タップの挙動をチェックのON/OFF切換とするようにしました。ディレクトリのタップ動作によりチェックが全てクリアされてしまうため、誤操作により何度もチェックをやり直す羽目になる問題があったためです。