はじめに
現在常用のスマホとしてAndroid端末を使っています。
(つい半年前まではiPhoneを使っていました)
端末機としては海外版、XperiaZ3 Compactです。
(昨今の大型化している流れの中軽量かつ音楽プレイヤーとしても優れている機種として選定しました。)
元々iPhoneユーザーかつ環境が全てApple製品で統一されているので、音楽管理もiTunesで行っています。
そういう状況下であったので、iSyncrというアプリでiTunesのプレイリストと端末を同期させて利用していました
どうもXperiaZ3 Compact側のイヤホンジャック(ヘッドホンジャック)が壊れたようで、
音楽プレイヤーとしてはまともには利用できなくなりました
とはいえ、Youtubeを見て修理するのはリスクが高いのと、パーツが個人輸入になるので調達までのハードルが高いので修理方向は諦めました
が、どうも最近のwalkmanでは、microSDを使えプレイリストを少し調整すれば認識できるようなことを聞きつけました
(店頭モックでも、プレイリストを認識し再生可能であることを確認しました)
なので、microSDとの同期はiSyncrに任せ、同期したプレイリストの変更を独自のアプリでやればいいのではないかと思い今回やってみました
やりたいこと
まずはiSyncrで作ったプレイリストがウォークマンから認識できるかどうかを確認しました。
結論的に言うとそのままでは不可能で、一旦編集が必要のようです。
どうやらiSyncrを使ってiTunesの曲を転送したときは、iSyncrの作ったm3uプレイリストを正しく認識出来ないようです
これは、ウォークマン側のプレイリスト内のパスの解釈の問題のようです。
対処方法としては、パスの先頭の「/」を削除することで認識させることできます
(ウォークマン内部では、パスが補完されているのかもしれませんがその辺りは不明です)
こんな感じになるようにします
1
2
/hoge/fuga.mp3 -> hoge/fuga.mp3
/hoge/fugafuga.mp3 -> hoge/fugafuga.mp3
ということで実装としてやりたいことは至ってシンプルです
編集後のプレイリストファイルを作成する
iSyncrで作成したプレイリストの内容を、ほぼそのまま編集後のプレイリストに書き込む
この時、行の最初の文字が「/」の場合、上記のように先頭の「/」を空文字に置換する
何故Groovy
Javaで書くと、以下の内容が面倒だったりするわけなのです
StreamだとかWriterやらReaderやらでコード自体が肥大化、冗長化すること
ファイルの中身の操作や置換とか結構大変で面倒
なので、今回その辺もサクッと実装できるGroovyで書いてみました
他にあるとすれば・・・
GroovyがAndroidでも動くということ
置換検証用のコードをサクッと移植したかったこと
個人的にGroovyでコード書くというリハビリ(書かなくなって久しいので)
当該Groovyコード
やりたいことを実施するファイルの置換用コードは以下のようになります
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def files = new File('Your Path').listFiles()
playList = files.findAll { it.getName() =~ /.*\.m3u$/ }
playList.each { file ->
def playListName = file.canonicalPath
// 変更版プレイリスト
def newPlayListName = "${file.canonicalPath}.new"
def newPlayListFile = new File(newPlayListName)
// 変更版プレイリストに現行プレイリストの修正内容を出力
newPlayListFile.withWriter('UTF-8') { writer ->
file.newReader().transformLine(writer) { line ->
if (line.startsWith("/")){
// 行の最初の文字の場合、「/」を置換する
line.replaceFirst(/\//,"")
} else {
// それ以外の場合はそのまま出力
line
}
}
}
// ファイル名を変更する
file.renameTo("${file.canonicalPath}.old")
newPlayListFile.renameTo("${playListName}")
}
本当であれば上書き保存という形にしたいところですが、
元のファイルが無くなってしまうと今度は同期が取れなくなる可能性があるので別ファイルとしました
GroovyをAndroidに導入
まずはGroovyのインストールをします。
以下のような形でsdkman を使いましょう
1
2
3
4
$ curl -s http://get.sdkman.io | bash
$ sdk ls groovy
$ sdk install groovy 2.4.5
$ sdk use groovy 2.4.5
次にAndroidStudioでAndoridプロジェクトを用意します
Androidプロジェクトに対して、groovy-android-gradle-plugin を導入します
プロジェクトROOTにあるbuild.gradleを以下のようにします
(プラグインを導入するのみです)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.3.0'
classpath 'org.codehaus.groovy:gradle-groovy-android-plugin:0.3.6'
}
}
allprojects {
repositories {
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
次にアプリ側のbuild.gradleを変更します
(プラグインの適用と、コンパイルライブラリの追加、プラグインの設定追加)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
apply plugin: 'com.android.application'
apply plugin: 'groovyx.grooid.groovy-android'
android {
中略
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.1.1'
compile 'com.android.support:design:23.1.1'
compile 'org.codehaus.groovy:groovy:2.4.3:grooid'
}
project.androidGroovy {
options {
configure(groovyOptions) {
encoding = 'UTF-8'
forkOptions.jvmArgs = ['-noverify'] // maybe necessary if you use Google Play Services
}
sourceCompatibility = '1.7'
targetCompatibility = '1.7'
}
}
ソースの配置を「src/main/java/MainActivity.java」
から「src/main/groovy/MainActivity.groovy」
変更します
最後にソースを以下のようにしてGroovyライクに実装します
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@CompileStatic
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
def hello = { lang ->
def toast = Toast.makeText(this, "Hello ${lang}!", Toast.LENGTH_LONG)
toast.setGravity(Gravity.CENTER, 0, 0)
toast.show()
}
hello("Hello Groovy on Android!")
}
}
一応いっておきますが、@CompileStaticアノテーションで静的コンパイルしなくても動作はします
が、動的コードはパフォーマンス的にきびしいので@CompileStatic推奨されているようです
ビルド時の注意点
Android Studioでビルドを実行したとき、エラーがあった場合以下のような形でEventLogに出力されてビルドが停止します。
メッセージを見ても、何が悪いか分かりません。正直、自分は困惑しました
1
2
3
4
5
6
7
0:52:06 Platform and Plugin Updates: The following components are ready to update: Google Play services, Google Repository, Android SDK Platform-tools
1:14:33 Gradle sync started
1:14:57 Gradle sync completed
1:14:57 Executing tasks: [:app:generateDebugSources, :app:generateDebugAndroidTestSources]
1:15:01 Gradle build finished in 4s 168ms
1:15:41 Executing tasks: [:app:generateDebugSources, :app:generateDebugAndroidTestSources, :app:compileDebugSources, :app:compileDebugAndroidTestSources]
1:15:47 ExternalSystemException: String index out of range: -105
そのときはGradle Consoleを確認しましょう
以下のような形でエラーメッセージが表示されています
(この場合、Syntaxエラーですね)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
:app:compileDebugJavaWithJavac UP-TO-DATE
:app:compileDebugGroovyWithGroovyc
startup failed:
MainActivity.groovy: 53: expecting ')', found '}' @ line 53, column 13.
}
^
1 error
FAILED
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':app:compileDebugGroovyWithGroovyc'.
> Compilation failed; see the compiler error output for details.
* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.
BUILD FAILED
Total time: 5.687 secs
因みに上記のような形になった場合、何故かそのままではビルドの再開が出来ませんでした。
対応としては一度「Clean Project」を実行する必要があります
結果的に・・・
ここまで読めば、分かる方はわかりますが実現はサクッとは出来ませんでした。
何故かと言うとAndroid4.4あたりから導入されたStorage Access Framework のことをスッカリ忘れていたのでやりたいことは出来ませんでしたorz
当初の運用自体は、iSyncrの同期先をSDカードにしてSDカードに対して同期
その後同期されたSDカードをPC側にマウントして、上記コードを通してやればよいので無理にAndroid経由させなくていいかなぁと思った次第です
とはいえGroovyでAndroidアプリを書いてみることはまぁできたのでよしとします。
GroovyでGroovy GDKの便利なAPIやクロージャの恩恵を最大限に受けてて、簡潔に書くことが出来て素晴らしいなと思いました