开源日报 每天推荐一个 GitHub 优质开源项目和一篇精选英文科技或编程文章原文,坚持阅读《开源日报》,保持每日学习的好习惯。
今日推荐开源项目:《当局者迷旁观者清 code-review-tips》
今日推荐英文原文:《You don’t have to be an Expert to contribute to Open Source》

今日推荐开源项目:《当局者迷旁观者清 code-review-tips》传送门:GitHub链接
推荐理由:当你写完一段代码并让它顺利的跑了起来:完美无缺,神来之笔;当你睡了一觉再回来看它:我昨天为什么写了这么个蠢玩意?这可能说的有点夸张了,但是很明显在你为团队项目写好一个模块或者别的什么之后,找一个同伴帮你看一下比你自己看来的更好——他没有这段代码的任何记忆,所以能重新检查每一个应该注意的点。这个项目是在代码审查中需要注意的点,包括可读性以及处理异常输入等,不仅是审查,自己写代码的时候也应该注意这些点。
今日推荐英文原文:《You don’t have to be an Expert to contribute to Open Source》作者:Albiona Hoti
原文链接:https://medium.com/@albionahh/you-dont-have-to-be-an-expert-to-contribute-to-open-source-21976c753a22
推荐理由:如何在不需要太高技术力的情况下为开源项目做贡献

You don’t have to be an Expert to contribute to Open Source

We think that to contribute to Open Source you have to know everything related to that technology, as the programming language, frameworks etc. But that’s not true, here is why!

Voice is an AudioBook player, which is open source and is developed with Kotlin for Android.

As a user of Voice AudioBook player, I wanted to be able to skip silence so I could finish the book faster. So I searched for issues in Voice apps repository, and I saw there was a feature request for skipping silence, so I started to work on it. Disclaimer: I have no Kotlin programming experience.

The article will contain:
  1. Skip Silence Feature description
  2. Copying and adapting ‘Jump to’ and ‘sleep’ feature
  3. Adding the skip silence feature
  4. Current and Updated implementation
  5. Final changes — Adding Migrations
  6. Takeaways

Skip Silence Feature description

Before you start to implement something, you have to find a similar functionality with the one you are thinking to work on, otherwise, it might be a project with a lot of modules and to really understand how everything works it might take you a lot of time.

The current

The final result

For my case, the most similar functionality was ‘Jump to’ that displaces you to a specific part of the book.

So I searched for that feature on the source code. I decided to add the skip silence feature as part of the menu because:
  1. There were other functionalities and I wanted to have the skip silence feature too.
  2. A menu you could manipulate with the listening part of audiobook.
This is how our final change will look like!

Copying and adapting ‘Jump to’ feature

I found the ‘Jump to’ in book_play.xml file which was an Item view:
<item
    android:id="@+id/action_time_change"
 android:title="@string/action_time_change"
    app:showAsAction="never"
/>
So I added an Item to skip silence:
<item
    android:id="@+id/action_skip_silence"
    android:title="@string/skip_silence"
    android:checkable="true"
    app:showAsAction="never"
/>
From there, searching the source code with the id of the ‘Jump to’ view send me in BookPlayController.kt file, where there was the code of that action as:
R.id.action_time_change -> {
launchJumpToPositionDialog()
  true
}
So I continued to add the changes for the skipping audio silence:

I added the function below to BookPlayController.kt that gets triggered when clicking the skip silence menu item.
R.id.action_skip_silence -> {
  toggleSkipSilenceState()
  true // goes for setOnMenuItemClickListener
}
Following the ‘Jump to’ functionality, send me to the function:
launchJumpToPositionDialog()
which was displaying a fragment and it wasn’t something I wanted to do.I just had to Skip Silence ?

Sadly this is all where skip silence led us to.

Copying and adapting ‘sleep’ feature

There was another item called Sleep-Timer and followed it’s id same as the earlier item, and it sends me to this action:
R.id.action_sleep -> {
  presenter.toggleSleepTimer()
  true
}
Investigated about the presenter and found out it was a presenter of bookplaycontroller.kt

Wondering what is this presenter? Didn’t care that much, found out it was a presenter of BookPlayController.kt so I looked over the BookPlayPresenter.kt — There I found this function:
override fun toggleSleepTimer() {
  if (sleepTimer.sleepTimerActive()) sleepTimer.setActive(false)
  else {
    view.openSleepTimeDialog()
  }
}
Hm, but sleepTimer was a class which had the functionality of sleeptTimer... Searching in BookPlayPresenter.kt I found the action below:
override fun seekTo(position: Int, file: File?) {
  val book = bookRepository.bookById(bookId)
      ?: return
  playerController.changePosition(position, file ?: book.content.currentFile)
}
The method changePosition directly manipulates with the audiobook player and my functionality would do the same.

Investigating into it:
fun changePosition(time: Int, file: File) {
  fire(
    intent(ACTION_CHANGE).apply {
      putExtra(CHANGE_TIME, time)
      putExtra(CHANGE_FILE, file.absolutePath)
    }
  )
}
So I replicated the above functionalities by adding the skipSilence method in playBookPresenter.kt
override fun toggleSkipSilence() {
  val skipSilence = bookRepository.bookById(bookId)?.content?.skipSilence
      ?: return
  playerController.setSkipSilence(!skipSilence)
}
And added the setSkipSilence() function as shown below:
fun setSkipSilence(skip: Boolean) {
  fire(
      intent(ACTION_SKIP_SILENCE).apply {
        putExtra(SKIP_SILENCE, skip)
      }
  )
}
Same as ACTION_CHANGE I looked at how I can create the ACTION_SKIP_SILENCE variables and there they were as companion objects:
const val ACTION_SPEED = "de.ph1b.audiobook.ACTION_SPEED"
const val ACTION_SKIP_SILENCE ="de.ph1b.audiobook.ACTION_SKIP_SILENCE"
I followed where the ACTION_SPEED variable was used and addressed me in PlayBackService.kt where all actions functionalities were written, for example, the ACTION_PLAY_PAUSE :
PlayerController.ACTION_PLAY_PAUSE -> {
  if (playStateManager.playState == PlayState.PLAYING) {
    player.pause(true)
  } else player.play()
}
The player called another function into it player.play() and the play() function was written in MediaPlayer.kt file.

So same how this method was created I started to add another method for ACTION_SKIP_SILENCE as below:
PlayerController.ACTION_SKIP_SILENCE -> {
  val skipSilences = intent.getBooleanExtra(PlayerController.SKIP_SILENCE, false)
  player.setSkipSilences(skipSilences)
}
Where I created the function setSkipSilences in theMediaPlayer.kt file:
fun setSkipSilences(skip: Boolean) {
  bookContent?.let {
    val copy = it.copy(skipSilence = skip)
    _bookContent.onNext(copy)
    player.setPlaybackParameters(it.playbackSpeed, skip)
  }
}
— setPlayBackParameters() was a method which had been created and it accepted only the playbackSpeed parameter. (During the time I was searching this project I found this method which started to play the audiobook and I thought this was the place I have to add a skip silence functionality)

Adding the skip silence functionality

The actual version of ExoPlayer that was being used in this project was 2.7.2 which didn’t have the capability of skipping silence, so I searched for skipping silence ExoPlayer in google and found this issue — “Skip silence” feature— which was implemented in version 2.8.0 of ExoPlayer. So I switch to the new version.

Current implementation

The current implementation of ExoPlayer had only the speed feature.
fun SimpleExoPlayer.setPlaybackSpeed(speed: Float) {                                                      if (playbackParameters?.speed != speed) {                                      
    playbackParameters = PlaybackParameters(speed, 1F)
  }
}

Updated implementation

With the updated one, you can now see the skipSilence argument.
fun SimpleExoPlayer.setPlaybackParameters(speed: Float, skipSilence: Boolean){
if (playbackParameters?.speed != speed ||     
    playbackParameters?.skipSilence != skipSilence) {                                          playbackParameters = PlaybackParameters(speed, 1F, skipSilence)                                        }                               
}
And I continued all the changes to connect the initial changes with the final one.

Final changes — Adding Migrations

Every book can have skip silence turned on or off, which required changes in the persistence layer which included adding the property in the files -> BookFactory, BookContent, and BookStorage.

Add a new migration on PersistanceModule to add the new property to all the previous books by default being false:
class Migration44to45 : IncrementalMigration(44) {                                   
  override fun migrate(db: SupportSQLiteDatabase) {                                     
    db.execSQL("ALTER TABLE tableBooks ADD skipSilence INTEGER")                                     
  }                                
}

Takeaways

  1. To implement new functionality in already existing software is not difficult as long as you can find a similar functionality that will guide you through the structure of the project.
  2. Even if you are learning a new technology, this is the path I recommend when learning a new technology, because trying to read through all the books, and all the knowledge will lead you into Analysis Paralysis or how I like to call it Learning Paralysis.
  3. Honestly, not all projects are nearly as good structured and architected as Voice Audiobook Player is which is developed by Paul Woitaschek, if you listen to audiobooks, I encourage you to give it a try.
  4. Last but not least, version 4.0 is released on 26th of December and you can use the skip silence feature ?

下载开源日报APP:https://openingsource.org/2579/
加入我们:https://openingsource.org/about/join/
关注我们:https://openingsource.org/about/love/