google / google/oboe

Change Play Audio Device there is a chance that it will fail

Open
#2,111 6 comments 0 reactions 2 assignees Claimed by @robertwu1 View on GitHub
bug
Dominant language
C++
Stars
4.1k
Forks
639
Avg merge
2d 23h
Merged PRs (30d)
8

Description

Android version(s): Android 13
Android device(s): Mi 11 Ultra
Oboe version: 1.9.2
App name used for testing:
(Please try to reproduce the issue using the OboeTester or an Oboe sample.)

**Short description**
(Please only report one bug per Issue. Do not combine multiple bugs.)
When I use the speaker to play, and then plug in the wired headphones, there is a probability that the headphones will fail to play.

**Steps to reproduce**
1. Build an Oboe for playback, using speakers by default.
2. Device changes are monitored through ```audioManager?.registerAudioDeviceCallback()``` in the application layer. When a wired headset is detected, stop and release of oboe are called in sequence to destroy the previous instance, and then reopen and start it again.

**Expected behavior**
Sound plays from headphones
**Actual behavior**
No sound from headphones
**Device**

Please list which devices have this bug.
If device specific, and you are on Linux or a Macintosh, connect the device and please share the result for the following script. This gets properties of the device.

```
for p in \
ro.product.brand ro.product.manufacturer ro.product.model \
ro.product.device ro.product.cpu.abi ro.build.description \
ro.hardware ro.hardware.chipname ro.arch "| grep aaudio";
do echo "$p = $(adb shell getprop $p)"; done
```

**Any additional context**

```
2024-10-24 11:53:01.326 1720-27007 MSPushStre...udioDevice I LivePushCC 27007:BiliAudioDevice.cpp:159 stop() stream_ state:Started
2024-10-24 11:53:01.326 1720-27007 AAudio D AAudioStream_requestStop(s#3) called
2024-10-24 11:53:01.326 1720-27007 AAudioStream D setState(s#3) from 4 to 9
2024-10-24 11:53:01.326 1720-27007 AudioTrack D stop(sessionID=2761)
2024-10-24 11:53:01.326 1720-27007 AudioTrack D stop(1210): prior state:STATE_ACTIVE
2024-10-24 11:53:01.327 1720-27007 AudioTrack D stop(1210): called with 4172352 frames delivered
2024-10-24 11:53:01.327 1720-27007 AAudioStream D setState(s#3) from 9 to 10
2024-10-24 11:53:01.327 1720-27007 MSPushStre...udioDevice I LivePushCC 27007:BiliAudioDevice.cpp:180 release() close....
2024-10-24 11:53:01.327 1720-27007 AAudio D AAudioStream_requestStop(s#3) called
2024-10-24 11:53:01.335 1720-1720 DecorView[] D onWindowFocusChanged hasWindowFocus false
2024-10-24 11:53:01.338 1720-27007 AAudio D AAudioStream_close(s#3) called ---------------
2024-10-24 11:53:01.338 1720-27007 AAudioStream D setState(s#3) from 10 to 11
2024-10-24 11:53:01.338 1720-27007 AudioTrack D stop(sessionID=2761)
2024-10-24 11:53:01.338 1720-27007 AudioTrack D stop(1210): prior state:STATE_STOPPED
2024-10-24 11:53:01.338 1720-1720 IntranetTrackPlugin D index: sky-eye-fps-android, msg: {current=55}
2024-10-24 11:53:01.339 1720-27007 AudioTrack D ~AudioTrack(sessionID=2761)
2024-10-24 11:53:01.339 1720-27007 AudioTrack D stop(sessionID=2761)
2024-10-24 11:53:01.340 1720-27007 AudioTrack D stop(1210): prior state:STATE_STOPPED
2024-10-24 11:53:01.341 1720-27007 AAudioStream D setState(s#3) from 11 to 12
2024-10-24 11:53:01.342 1720-27007 AAudioStream D ~AudioStream(s#3) mPlayerBase strongCount = 2
2024-10-24 11:53:01.342 1720-27007 AAudio D AAudioStream_close(s#3) returned 0 ---------
2024-10-24 11:53:01.342 1720-27007 MSPushStre...udioDevice I LivePushCC 27007:BiliAudioDevice.cpp:185 release() reset....
2024-10-24 11:53:01.342 1720-27007 OboeAudio D Destructor for AudioStream at 0xb400007bb97eee80
2024-10-24 11:53:01.342 1720-27007 LiveStream...PlayOutput I nativeAudioDeviceStop, ret:0
2024-10-24 11:53:01.343 1720-27007 LiveStream...PlayOutput I openPlayStream, handler is zero?:false, streamConfig:nativeAPI:AAudio,BufferCapacityInFrames:0,FramesPerBurst:0,ChannelCount:2,ChannelMask:Stereo,DeviceId:1211,SessionId:-1,Format:PCM_Float,SampleRate:48000,SharingMode:shared,PerformanceMode:LL,InputPreset:VoiceRec,Usage:Media,ContentType:Music,FormatConversionAllowed:false,ChannelConversionAllowed:false,RateConversionQuality:None,HardwareChannelCount:0,HardwareSampleRate:0,HardwareFormat:0
2024-10-24 11:53:01.346 1720-27007 MSPushStre...udioDevice I LivePushCC 27007:BiliAudioDevice.cpp:81 open() openStream.... direction:OUTPUT, deviceID:1211
2024-10-24 11:53:01.347 1720-27007 OboeAudio I openStreamInternal() OUTPUT -------- OboeVersion1.9.2 --------
2024-10-24 11:53:01.347 1720-27007 OboeAudio D Constructor for AudioStream at 0xb400007bb97eee80
2024-10-24 11:53:01.347 1720-27007 AAudio I AAudioStreamBuilder_openStream() called ----------------------------------------
2024-10-24 11:53:01.347 1720-27007 AudioStreamBuilder I rate = 48000, channels = 2, channelMask = 0x3, format = 5, sharing = SH, dir = OUTPUT
2024-10-24 11:53:01.347 1720-27007 AudioStreamBuilder I device = 1211, sessionId = -1, perfMode = 12, callback: ON with frames = 0
2024-10-24 11:53:01.347 1720-27007 AudioStreamBuilder I usage = 1, contentType = 2, inputPreset = 6, allowedCapturePolicy = 0
2024-10-24 11:53:01.347 1720-27007 AudioStreamBuilder I privacy sensitive = false, opPackageName = (null), attributionTag = (null)
2024-10-24 11:53:01.347 1720-1720 DecorView[] D onWindowFocusChanged hasWindowFocus true
2024-10-24 11:53:01.347 1720-1720 HandWritingStubImpl I refreshLastKeyboardType: 1
2024-10-24 11:53:01.348 1720-1720 HandWritingStubImpl I getCurrentKeyboardType: 1
2024-10-24 11:53:01.349 1720-27007 li.bilibililive D PlayerBase::PlayerBase()
2024-10-24 11:53:01.350 1720-27007 AudioStreamTrack D open(), request notificationFrames = -8, frameCount = 0
2024-10-24 11:53:01.350 1720-27007 AudioTrack D set(sessionId=0)
2024-10-24 11:53:01.350 1720-27007 AudioTrack D set(): streamType -1, sampleRate 48000, format 0x5, channelMask 0x3, frameCount 0, flags #104, notificationFrames -8, sessionId 0, transferType 1, uid -1, pid -1
2024-10-24 11:53:01.367 1720-27007 AudioTrack I createTrack_l(1210): AUDIO_OUTPUT_FLAG_FAST successful; frameCount 0 -> 1536
2024-10-24 11:53:01.368 1720-27007 AudioTrack I AudioTrack::setVolume L:1.000000 R:1.000000 and the pakage is com.bilibili.bilibililive
2024-10-24 11:53:01.368 1720-27007 AudioTrack I AudioTrack::setVolume L:1.000000 R:1.000000
2024-10-24 11:53:01.369 1720-27007 AudioStreamTrack D open() flags changed from 0x00000104 to 0x00000004
2024-10-24 11:53:01.370 1720-27007 AAudioStream D setState(s#4) from 0 to 2
2024-10-24 11:53:01.372 1720-27007 AAudio I AAudioStreamBuilder_openStream() returns 0 = AAUDIO_OK for s#4 ----------------
2024-10-24 11:53:01.372 1720-27007 OboeAudio D AudioStreamAAudio.open() format=2, sampleRate=48000, capacity = 1536
2024-10-24 11:53:01.374 1720-27007 OboeAudio D calculateDefaultDelayBeforeCloseMillis() default = 10
2024-10-24 11:53:01.374 1720-27007 OboeAudio D AudioStreamAAudio.open: AAudioStream_Open() returned AAUDIO_OK = 0
2024-10-24 11:53:01.375 1720-27007 LiveStream...PlayOutput I nativeAudioDeviceOpen,ret:0
2024-10-24 11:53:01.378 1720-27007 MSPushStre...udioDevice I LivePushCC 27007:BiliAudioDevice.cpp:105 start() stream state:Unknown, start...
2024-10-24 11:53:01.378 1720-27007 AAudio D AAudioStream_requestStart(s#4) called --------------
2024-10-24 11:53:01.378 1720-27007 AAudioStream D setState(s#4) from 2 to 3
2024-10-24 11:53:01.378 1720-27007 AudioTrack D start(1224): prior state:STATE_STOPPED
2024-10-24 11:53:01.400 1720-27007 AAudio D AAudioStream_requestStart(s#4) returned 0 ---------
2024-10-24 11:53:01.400 1720-27218 MSPushStre...udioDevice I LivePushCC 27218:BiliAudioDevice.cpp:464 onAudioReady() onAudioDeviceDataReady.... direction:OUTPUT, currentSelectDeviceID:1211, streamState:Started
2024-10-24 11:53:01.401 1720-27007 LiveStream...PlayOutput I nativeAudioDeviceStart, ret:0
2024-10-24 11:53:01.406 1720-27218 AAudioStream D setState(s#4) from 3 to 4
2024-10-24 11:53:01.690 1720-25554 BpBinder W PerfMonitor binderTransact : time=288ms interface=android.media.IAudioPolicyService code=44
2024-10-24 11:53:01.690 1720-25554 AudioStreamLegacy D onAudioDeviceUpdate(deviceId = 3)
2024-10-24 11:53:01.691 1720-25554 AudioStreamLegacy D onAudioDeviceUpdate() request DISCONNECT in data callback, device 1211 => 3
2024-10-24 11:53:01.691 1720-25554 AudioStreamLegacy D onAudioDeviceUpdate(deviceId = 1211)
2024-10-24 11:53:01.691 1720-25554 AudioStreamLegacy D onAudioDeviceUpdate() request DISCONNECT in data callback, device 3 => 1211
2024-10-24 11:53:01.694 1720-27218 AudioStreamLegacy D checkForDisconnectRequest() mRequestDisconnect acknowledged
2024-10-24 11:53:01.694 1720-27218 AAudioStream D setState(s#4) from 4 to 13
2024-10-24 11:53:01.694 1720-27218 AudioStreamLegacy W onMoreData() data, stream disconnected
2024-10-24 11:53:01.694 1720-27218 AudioTrack E processAudioBuffer(1224): EVENT_MORE_DATA requested 1536 bytes but callback returned -1 bytes
2024-10-24 11:53:01.694 1720-27219 OboeAudio D oboe_aaudio_error_thread_proc_shared(,-899) - entering >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
2024-10-24 11:53:01.694 1720-27219 MSPushStre...orCallback W LivePushCC 27219:BiliAudioDevice.cpp:480 onError() onError!!! deviceId:1211, current direction is:OUTPUT, audioDevice, msg:AAUDIO_ERROR_DISCONNECTED: Unknown error -899
2024-10-24 11:53:01.694 1720-27219 LiveStream...PlayOutput I onError, message:AAUDIO_ERROR_DISCONNECTED: Unknown error -899, code:2
2024-10-24 11:53:01.694 1720-27219 AAudio D AAudioStream_requestStop(s#4) called
2024-10-24 11:53:01.694 1720-27219 AAudioStream D setState(s#4) from 13 to 9
2024-10-24 11:53:01.694 1720-27219 AAudioStream W setState(4) tried to set to 9 but already DISCONNECTED
2024-10-24 11:53:01.694 1720-27219 AudioTrack D stop(sessionID=2769)
2024-10-24 11:53:01.694 1720-27219 AudioTrack D stop(1224): prior state:STATE_ACTIVE
2024-10-24 11:53:01.694 1720-27219 AudioTrack D stop(1224): called with 7296 frames delivered
2024-10-24 11:53:01.695 1720-27219 AudioStreamLegacy D checkForDisconnectRequest() mRequestDisconnect acknowledged
2024-10-24 11:53:01.696 1720-27219 MSPushStre...orCallback W LivePushCC 27219:BiliAudioDevice.cpp:504 onErrorBeforeClose() onErrorBeforeClose!!! deviceId:1211, current direction is:OUTPUT, audioDevice, msg:AAUDIO_ERROR_DISCONNECTED: Unknown error -899
2024-10-24 11:53:01.696 1720-27219 LiveStream...PlayOutput I onErrorBeforeClose, message:AAUDIO_ERROR_DISCONNECTED: Unknown error -899, code:2
2024-10-24 11:53:01.696 1720-27219 AAudio D AAudioStream_requestStop(s#4) called
2024-10-24 11:53:01.696 1720-27219 AAudioStream D setState(s#4) from 13 to 9
2024-10-24 11:53:01.696 1720-27219 AAudioStream W setState(4) tried to set to 9 but already DISCONNECTED
2024-10-24 11:53:01.696 1720-27219 AudioTrack D stop(sessionID=2769)
2024-10-24 11:53:01.696 1720-27219 AudioTrack D stop(1224): prior state:STATE_STOPPED
2024-10-24 11:53:01.699 1720-25871 AidlConversion W aidl2legacy_AudioChannelLayout_audio_channel_mask_t: no legacy output audio_channel_mask_t found for AudioChannelLayout{layoutMask: 16}
2024-10-24 11:53:01.706 1720-27219 AAudio D AAudioStream_close(s#4) called ---------------
2024-10-24 11:53:01.706 1720-27219 AAudioStream D setState(s#4) from 13 to 11
2024-10-24 11:53:01.706 1720-27219 AudioTrack D stop(sessionID=2769)
2024-10-24 11:53:01.706 1720-27219 AudioTrack D stop(1224): prior state:STATE_STOPPED
2024-10-24 11:53:01.706 1720-27219 AudioTrack D ~AudioTrack(sessionID=2769)
2024-10-24 11:53:01.706 1720-27219 AudioTrack D stop(sessionID=2769)
2024-10-24 11:53:01.706 1720-27219 AudioTrack D stop(1224): prior state:STATE_STOPPED
2024-10-24 11:53:01.711 1720-27219 AAudioStream D setState(s#4) from 11 to 12
2024-10-24 11:53:01.711 1720-27219 AAudioStream D ~AudioStream(s#4) mPlayerBase strongCount = 2
2024-10-24 11:53:01.711 1720-27219 AAudio D AAudioStream_close(s#4) returned 0 ---------
2024-10-24 11:53:01.712 1720-27219 MSPushStre...orCallback W LivePushCC 27219:BiliAudioDevice.cpp:492 onErrorAfterClose() onErrorAfterClose!!! deviceId:1211, current direction is:OUTPUT, audioDevice, msg:AAUDIO_ERROR_DISCONNECTED: Unknown error -899
2024-10-24 11:53:01.712 1720-27219 LiveStream...PlayOutput I onErrorAfterClose, message:AAUDIO_ERROR_DISCONNECTED: Unknown error -899, code:2
2024-10-24 11:53:01.712 1720-27219 OboeAudio D oboe_aaudio_error_thread_proc_shared() - exiting <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
```
In the log, we can actually see that when the restart switched to wired headphones, it was successful:
```
2024-10-24 11:53:01.372 1720-27007 AAudio I AAudioStreamBuilder_openStream() returns 0 = AAUDIO_OK for s#4 ----------------
2024-10-24 11:53:01.372 1720-27007 OboeAudio D AudioStreamAAudio.open() format=2, sampleRate=48000, capacity = 1536
2024-10-24 11:53:01.374 1720-27007 OboeAudio D calculateDefaultDelayBeforeCloseMillis() default = 10
2024-10-24 11:53:01.374 1720-27007 OboeAudio D AudioStreamAAudio.open: AAudioStream_Open() returned AAUDIO_OK = 0
2024-10-24 11:53:01.375 1720-27007 LiveStream...PlayOutput I nativeAudioDeviceOpen,ret:0
2024-10-24 11:53:01.378 1720-27007 MSPushStre...udioDevice I LivePushCC 27007:BiliAudioDevice.cpp:105 start() stream state:Unknown, start...
2024-10-24 11:53:01.378 1720-27007 AAudio D AAudioStream_requestStart(s#4) called --------------
2024-10-24 11:53:01.378 1720-27007 AAudioStream D setState(s#4) from 2 to 3
2024-10-24 11:53:01.378 1720-27007 AudioTrack D start(1224): prior state:STATE_STOPPED
2024-10-24 11:53:01.400 1720-27007 AAudio D AAudioStream_requestStart(s#4) returned 0 ---------
2024-10-24 11:53:01.400 1720-27218 MSPushStre...udioDevice I LivePushCC 27218:BiliAudioDevice.cpp:464 onAudioReady() onAudioDeviceDataReady.... direction:OUTPUT, currentSelectDeviceID:1211, streamState:Started
```
But then there will be a callback from the bottom layer, as follows:
```
2024-10-24 11:53:01.690 1720-25554 AudioStreamLegacy D onAudioDeviceUpdate(deviceId = 3)
2024-10-24 11:53:01.691 1720-25554 AudioStreamLegacy D onAudioDeviceUpdate() request DISCONNECT in data callback, device 1211 => 3
2024-10-24 11:53:01.691 1720-25554 AudioStreamLegacy D onAudioDeviceUpdate(deviceId = 1211)
2024-10-24 11:53:01.691 1720-25554 AudioStreamLegacy D onAudioDeviceUpdate() request DISCONNECT in data callback, device 3 => 1211
2024-10-24 11:53:01.694 1720-27218 AudioStreamLegacy D checkForDisconnectRequest() mRequestDisconnect acknowledged
2024-10-24 11:53:01.694 1720-27218 AAudioStream D setState(s#4) from 4 to 13
2024-10-24 11:53:01.694 1720-27218 AudioStreamLegacy W onMoreData() data, stream disconnected
2024-10-24 11:53:01.694 1720-27218 AudioTrack E processAudioBuffer(1224): EVENT_MORE_DATA requested 1536 bytes but callback returned -1 bytes
2024-10-24 11:53:01.694 1720-27219 OboeAudio D oboe_aaudio_error_thread_proc_shared(,-899) - entering >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
2024-10-24 11:53:01.694 1720-27219 MSPushStre...orCallback W LivePushCC 27219:BiliAudioDevice.cpp:480 onError() onError!!! deviceId:1211, current direction is:OUTPUT, audioDevice, msg:AAUDIO_ERROR_DISCONNECTED: Unknown error -899
2024-10-24 11:53:01.694 1720-27219 LiveStream...PlayOutput I onError, message:AAUDIO_ERROR_DISCONNECTED: Unknown error -899, code:2
```
At this time, the headset is not unplugged from the phone, but it receives the callback of onAudioDeviceUpdate. At this time, it will be from the device(1211) of the wired headset -> the device(3) of the speaker, and then there will be another one from the device(3) of the speaker -> the device(1211) of the wired headset. Then, because onError is thrown at this time, if onError returns false, then in oboe, it will stop and close it. At this time, there is no sound in the wired headset.

```
2024-10-24 11:53:01.690 1720-25554 AudioStreamLegacy D onAudioDeviceUpdate(deviceId = 3)
2024-10-24 11:53:01.691 1720-25554 AudioStreamLegacy D onAudioDeviceUpdate() request DISCONNECT in data callback, device 1211 => 3
2024-10-24 11:53:01.691 1720-25554 AudioStreamLegacy D onAudioDeviceUpdate(deviceId = 1211)
2024-10-24 11:53:01.691 1720-25554 AudioStreamLegacy D onAudioDeviceUpdate() request DISCONNECT in data callback, device 3 => 1211
2024-10-24 11:53:01.694 1720-27218 AudioStreamLegacy D checkForDisconnectRequest() mRequestDisconnect acknowledged
2024-10-24 11:53:01.694 1720-27218 AAudioStream D setState(s#4) from 4 to 13
2024-10-24 11:53:01.694 1720-27218 AudioStreamLegacy W onMoreData() data, stream disconnected
2024-10-24 11:53:01.694 1720-27218 AudioTrack E processAudioBuffer(1224): EVENT_MORE_DATA requested 1536 bytes but callback returned -1 bytes
2024-10-24 11:53:01.694 1720-27219 OboeAudio D oboe_aaudio_error_thread_proc_shared(,-899) - entering >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
2024-10-24 11:53:01.694 1720-27219 MSPushStre...orCallback W LivePushCC 27219:BiliAudioDevice.cpp:480 onError() onError!!! deviceId:1211, current direction is:OUTPUT, audioDevice, msg:AAUDIO_ERROR_DISCONNECTED: Unknown error -899
2024-10-24 11:53:01.694 1720-27219 LiveStream...PlayOutput I onError, message:AAUDIO_ERROR_DISCONNECTED: Unknown error -899, code:2
2024-10-24 11:53:01.694 1720-27219 AAudio D AAudioStream_requestStop(s#4) called
2024-10-24 11:53:01.694 1720-27219 AAudioStream D setState(s#4) from 13 to 9
2024-10-24 11:53:01.694 1720-27219 AAudioStream W setState(4) tried to set to 9 but already DISCONNECTED
2024-10-24 11:53:01.694 1720-27219 AudioTrack D stop(sessionID=2769)
2024-10-24 11:53:01.694 1720-27219 AudioTrack D stop(1224): prior state:STATE_ACTIVE
2024-10-24 11:53:01.694 1720-27219 AudioTrack D stop(1224): called with 7296 frames delivered
2024-10-24 11:53:01.695 1720-27219 AudioStreamLegacy D checkForDisconnectRequest() mRequestDisconnect acknowledged
2024-10-24 11:53:01.696 1720-27219 MSPushStre...orCallback W LivePushCC 27219:BiliAudioDevice.cpp:504 onErrorBeforeClose() onErrorBeforeClose!!! deviceId:1211, current direction is:OUTPUT, audioDevice, msg:AAUDIO_ERROR_DISCONNECTED: Unknown error -899
2024-10-24 11:53:01.696 1720-27219 LiveStream...PlayOutput I onErrorBeforeClose, message:AAUDIO_ERROR_DISCONNECTED: Unknown error -899, code:2
2024-10-24 11:53:01.696 1720-27219 AAudio D AAudioStream_requestStop(s#4) called
2024-10-24 11:53:01.696 1720-27219 AAudioStream D setState(s#4) from 13 to 9
2024-10-24 11:53:01.696 1720-27219 AAudioStream W setState(4) tried to set to 9 but already DISCONNECTED
2024-10-24 11:53:01.696 1720-27219 AudioTrack D stop(sessionID=2769)
2024-10-24 11:53:01.696 1720-27219 AudioTrack D stop(1224): prior state:STATE_STOPPED
2024-10-24 11:53:01.699 1720-25871 AidlConversion W aidl2legacy_AudioChannelLayout_audio_channel_mask_t: no legacy output audio_channel_mask_t found for AudioChannelLayout{layoutMask: 16}
2024-10-24 11:53:01.706 1720-27219 AAudio D AAudioStream_close(s#4) called ---------------
2024-10-24 11:53:01.706 1720-27219 AAudioStream D setState(s#4) from 13 to 11
2024-10-24 11:53:01.706 1720-27219 AudioTrack D stop(sessionID=2769)
2024-10-24 11:53:01.706 1720-27219 AudioTrack D stop(1224): prior state:STATE_STOPPED
```
I saw a similar question asked before. [How to handle audio output device change?](https://github.com/google/oboe/issues/1244).
have seen: [SimpleMultiPlayer::startStream](https://github.com/google/oboe/blob/main/samples/iolib/src/main/cpp/player/SimpleMultiPlayer.cpp#L118-L144) and [SimpleMultiPlayer::MyErrorCallback::onErrorAfterClose](https://github.com/google/oboe/blob/main/samples/iolib/src/main/cpp/player/SimpleMultiPlayer.cpp#L66-L73)
Is this to avoid switching device failure?

Looking forward to your reply, thank you

If applicable, please attach a few seconds of an uncompressed recording of the sound in a WAV or AIFF file.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.