開源日報 每天推薦一個 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/