Repository: JeffMony/MediaSDK
Branch: master
Commit: b9fc6524c5ec
Files: 946
Total size: 6.6 MB
Directory structure:
gitextract_ez39w3qr/
├── .gitignore
├── LICENSE
├── README.md
├── README_cn.md
├── androidasync/
│ ├── .gitignore
│ ├── androidasync.iml
│ ├── build.gradle
│ ├── consumer-rules.pro
│ ├── proguard-rules.pro
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ └── java/
│ └── com/
│ └── jeffmony/
│ └── async/
│ ├── AsyncDatagramSocket.java
│ ├── AsyncNetworkSocket.java
│ ├── AsyncSSLException.java
│ ├── AsyncSSLServerSocket.java
│ ├── AsyncSSLSocket.java
│ ├── AsyncSSLSocketWrapper.java
│ ├── AsyncSemaphore.java
│ ├── AsyncServer.java
│ ├── AsyncServerSocket.java
│ ├── AsyncSocket.java
│ ├── BufferedDataSink.java
│ ├── ByteBufferList.java
│ ├── ChannelWrapper.java
│ ├── DataEmitter.java
│ ├── DataEmitterBase.java
│ ├── DataEmitterReader.java
│ ├── DataSink.java
│ ├── DataTrackingEmitter.java
│ ├── DatagramChannelWrapper.java
│ ├── FileDataEmitter.java
│ ├── FilteredDataEmitter.java
│ ├── FilteredDataSink.java
│ ├── HostnameResolutionException.java
│ ├── LineEmitter.java
│ ├── PushParser.java
│ ├── SelectorWrapper.java
│ ├── ServerSocketChannelWrapper.java
│ ├── SocketChannelWrapper.java
│ ├── TapCallback.java
│ ├── ThreadQueue.java
│ ├── Util.java
│ ├── ZipDataSink.java
│ ├── callback/
│ │ ├── CompletedCallback.java
│ │ ├── ConnectCallback.java
│ │ ├── ContinuationCallback.java
│ │ ├── DataCallback.java
│ │ ├── ListenCallback.java
│ │ ├── ResultCallback.java
│ │ ├── SocketCreateCallback.java
│ │ ├── ValueCallback.java
│ │ ├── ValueFunction.java
│ │ └── WritableCallback.java
│ ├── dns/
│ │ ├── Dns.java
│ │ └── DnsResponse.java
│ ├── future/
│ │ ├── Cancellable.java
│ │ ├── Continuation.java
│ │ ├── Converter.java
│ │ ├── DependentCancellable.java
│ │ ├── DependentFuture.java
│ │ ├── DoneCallback.java
│ │ ├── FailCallback.java
│ │ ├── FailConvertCallback.java
│ │ ├── FailRecoverCallback.java
│ │ ├── Future.java
│ │ ├── FutureCallback.java
│ │ ├── FutureRunnable.java
│ │ ├── FutureThread.java
│ │ ├── Futures.java
│ │ ├── HandlerFuture.java
│ │ ├── MultiFuture.java
│ │ ├── MultiTransformFuture.java
│ │ ├── SimpleCancellable.java
│ │ ├── SimpleFuture.java
│ │ ├── SuccessCallback.java
│ │ ├── ThenCallback.java
│ │ ├── ThenFutureCallback.java
│ │ ├── TransformFuture.java
│ │ └── TypeConverter.java
│ ├── http/
│ │ ├── AsyncHttpClient.java
│ │ ├── AsyncHttpClientMiddleware.java
│ │ ├── AsyncHttpDelete.java
│ │ ├── AsyncHttpGet.java
│ │ ├── AsyncHttpHead.java
│ │ ├── AsyncHttpPost.java
│ │ ├── AsyncHttpPut.java
│ │ ├── AsyncHttpRequest.java
│ │ ├── AsyncHttpResponse.java
│ │ ├── AsyncHttpResponseImpl.java
│ │ ├── AsyncSSLEngineConfigurator.java
│ │ ├── AsyncSSLSocketMiddleware.java
│ │ ├── AsyncSocketMiddleware.java
│ │ ├── BasicNameValuePair.java
│ │ ├── BodyDecoderException.java
│ │ ├── ConnectionClosedException.java
│ │ ├── ConnectionFailedException.java
│ │ ├── Headers.java
│ │ ├── HttpDate.java
│ │ ├── HttpTransportMiddleware.java
│ │ ├── HttpUtil.java
│ │ ├── HybiParser.java
│ │ ├── Multimap.java
│ │ ├── NameValuePair.java
│ │ ├── Protocol.java
│ │ ├── ProtocolVersion.java
│ │ ├── RedirectLimitExceededException.java
│ │ ├── RequestLine.java
│ │ ├── SSLEngineSNIConfigurator.java
│ │ ├── SimpleMiddleware.java
│ │ ├── TaggedList.java
│ │ ├── WebSocket.java
│ │ ├── WebSocketHandshakeException.java
│ │ ├── WebSocketImpl.java
│ │ ├── body/
│ │ │ ├── AsyncHttpRequestBody.java
│ │ │ ├── ByteBufferListRequestBody.java
│ │ │ ├── DocumentBody.java
│ │ │ ├── FileBody.java
│ │ │ ├── FilePart.java
│ │ │ ├── JSONArrayBody.java
│ │ │ ├── JSONObjectBody.java
│ │ │ ├── MultipartFormDataBody.java
│ │ │ ├── Part.java
│ │ │ ├── StreamBody.java
│ │ │ ├── StreamPart.java
│ │ │ ├── StringBody.java
│ │ │ ├── StringPart.java
│ │ │ └── UrlEncodedFormBody.java
│ │ ├── cache/
│ │ │ ├── HeaderParser.java
│ │ │ ├── Objects.java
│ │ │ ├── RawHeaders.java
│ │ │ ├── RequestHeaders.java
│ │ │ ├── ResponseCacheMiddleware.java
│ │ │ ├── ResponseHeaders.java
│ │ │ ├── ResponseSource.java
│ │ │ └── StrictLineReader.java
│ │ ├── callback/
│ │ │ ├── HttpConnectCallback.java
│ │ │ └── RequestCallback.java
│ │ ├── filter/
│ │ │ ├── ChunkedDataException.java
│ │ │ ├── ChunkedInputFilter.java
│ │ │ ├── ChunkedOutputFilter.java
│ │ │ ├── ContentLengthFilter.java
│ │ │ ├── DataRemainingException.java
│ │ │ ├── GZIPInputFilter.java
│ │ │ ├── InflaterInputFilter.java
│ │ │ └── PrematureDataEndException.java
│ │ └── server/
│ │ ├── AsyncHttpRequestBodyProvider.java
│ │ ├── AsyncHttpServer.java
│ │ ├── AsyncHttpServerRequest.java
│ │ ├── AsyncHttpServerRequestImpl.java
│ │ ├── AsyncHttpServerResponse.java
│ │ ├── AsyncHttpServerResponseImpl.java
│ │ ├── AsyncHttpServerRouter.java
│ │ ├── AsyncProxyServer.java
│ │ ├── BoundaryEmitter.java
│ │ ├── HttpServerRequestCallback.java
│ │ ├── MalformedRangeException.java
│ │ ├── MimeEncodingException.java
│ │ ├── RouteMatcher.java
│ │ ├── StreamSkipException.java
│ │ └── UnknownRequestBody.java
│ ├── parser/
│ │ ├── AsyncParser.java
│ │ ├── ByteBufferListParser.java
│ │ ├── DocumentParser.java
│ │ ├── JSONArrayParser.java
│ │ ├── JSONObjectParser.java
│ │ └── StringParser.java
│ ├── stream/
│ │ ├── ByteBufferListInputStream.java
│ │ ├── FileDataSink.java
│ │ ├── InputStreamDataEmitter.java
│ │ ├── OutputStreamDataCallback.java
│ │ └── OutputStreamDataSink.java
│ ├── util/
│ │ ├── Allocator.java
│ │ ├── ArrayDeque.java
│ │ ├── Charsets.java
│ │ ├── Deque.java
│ │ ├── FileCache.java
│ │ ├── FileUtility.java
│ │ ├── HashList.java
│ │ ├── IdleTimeout.java
│ │ ├── LruCache.java
│ │ ├── StreamUtility.java
│ │ ├── TaggedList.java
│ │ ├── ThrottleTimeout.java
│ │ ├── TimeoutBase.java
│ │ └── UntypedHashtable.java
│ └── wrapper/
│ ├── AsyncSocketWrapper.java
│ └── DataEmitterWrapper.java
├── app/
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ ├── assets/
│ │ └── list.json
│ ├── java/
│ │ └── com/
│ │ └── android/
│ │ └── media/
│ │ ├── DownloadBaseListActivity.java
│ │ ├── DownloadFeatureActivity.java
│ │ ├── DownloadOrcodeActivity.java
│ │ ├── DownloadPlayActivity.java
│ │ ├── DownloadSettingsActivity.java
│ │ ├── MainActivity.java
│ │ ├── MediaScannerActivity.java
│ │ ├── MyApplication.java
│ │ ├── PlayFeatureActivity.java
│ │ ├── PlayerActivity.java
│ │ └── VideoListAdapter.java
│ └── res/
│ ├── drawable/
│ │ ├── border.xml
│ │ └── ic_launcher_background.xml
│ ├── drawable-v24/
│ │ └── ic_launcher_foreground.xml
│ ├── layout/
│ │ ├── activity_download_feature.xml
│ │ ├── activity_download_list.xml
│ │ ├── activity_download_play.xml
│ │ ├── activity_download_settings.xml
│ │ ├── activity_main.xml
│ │ ├── activity_orcode.xml
│ │ ├── activity_play_func.xml
│ │ ├── activity_player.xml
│ │ ├── activity_scanner.xml
│ │ ├── download_item.xml
│ │ └── video_item.xml
│ ├── mipmap-anydpi-v26/
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ └── values/
│ ├── colors.xml
│ ├── dimens.xml
│ ├── strings.xml
│ └── styles.xml
├── base/
│ ├── .gitignore
│ ├── base.iml
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ ├── java/
│ │ └── com/
│ │ └── android/
│ │ └── baselib/
│ │ ├── MediaSDKReceiver.java
│ │ ├── NetworkCallbackImpl.java
│ │ ├── NetworkListener.java
│ │ ├── WeakHandler.java
│ │ └── utils/
│ │ ├── LogUtils.java
│ │ ├── NetworkUtils.java
│ │ ├── ScreenUtils.java
│ │ └── Utility.java
│ └── res/
│ └── values/
│ └── strings.xml
├── build.gradle
├── constants.gradle
├── exoplayer/
│ ├── .gitignore
│ ├── build.gradle
│ ├── exoplayer.iml
│ ├── proguard-rules.pro
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ ├── java/
│ │ └── com/
│ │ └── google/
│ │ └── android/
│ │ └── exoplayer2/
│ │ ├── AudioBecomingNoisyManager.java
│ │ ├── AudioFocusManager.java
│ │ ├── BasePlayer.java
│ │ ├── BaseRenderer.java
│ │ ├── C.java
│ │ ├── ControlDispatcher.java
│ │ ├── DefaultControlDispatcher.java
│ │ ├── DefaultLoadControl.java
│ │ ├── DefaultMediaClock.java
│ │ ├── DefaultRenderersFactory.java
│ │ ├── ExoPlaybackException.java
│ │ ├── ExoPlayer.java
│ │ ├── ExoPlayerFactory.java
│ │ ├── ExoPlayerImpl.java
│ │ ├── ExoPlayerImplInternal.java
│ │ ├── ExoPlayerLibraryInfo.java
│ │ ├── Format.java
│ │ ├── FormatHolder.java
│ │ ├── IllegalSeekPositionException.java
│ │ ├── LoadControl.java
│ │ ├── MediaPeriodHolder.java
│ │ ├── MediaPeriodInfo.java
│ │ ├── MediaPeriodQueue.java
│ │ ├── NoSampleRenderer.java
│ │ ├── ParserException.java
│ │ ├── PlaybackInfo.java
│ │ ├── PlaybackParameters.java
│ │ ├── PlaybackPreparer.java
│ │ ├── Player.java
│ │ ├── PlayerMessage.java
│ │ ├── Renderer.java
│ │ ├── RendererCapabilities.java
│ │ ├── RendererConfiguration.java
│ │ ├── RenderersFactory.java
│ │ ├── SeekParameters.java
│ │ ├── SimpleExoPlayer.java
│ │ ├── Timeline.java
│ │ ├── WakeLockManager.java
│ │ ├── analytics/
│ │ │ ├── AnalyticsCollector.java
│ │ │ ├── AnalyticsListener.java
│ │ │ ├── DefaultAnalyticsListener.java
│ │ │ ├── DefaultPlaybackSessionManager.java
│ │ │ ├── PlaybackSessionManager.java
│ │ │ ├── PlaybackStats.java
│ │ │ ├── PlaybackStatsListener.java
│ │ │ └── package-info.java
│ │ ├── audio/
│ │ │ ├── Ac3Util.java
│ │ │ ├── Ac4Util.java
│ │ │ ├── AudioAttributes.java
│ │ │ ├── AudioCapabilities.java
│ │ │ ├── AudioCapabilitiesReceiver.java
│ │ │ ├── AudioDecoderException.java
│ │ │ ├── AudioListener.java
│ │ │ ├── AudioProcessor.java
│ │ │ ├── AudioRendererEventListener.java
│ │ │ ├── AudioSink.java
│ │ │ ├── AudioTimestampPoller.java
│ │ │ ├── AudioTrackPositionTracker.java
│ │ │ ├── AuxEffectInfo.java
│ │ │ ├── BaseAudioProcessor.java
│ │ │ ├── ChannelMappingAudioProcessor.java
│ │ │ ├── DefaultAudioSink.java
│ │ │ ├── DtsUtil.java
│ │ │ ├── FloatResamplingAudioProcessor.java
│ │ │ ├── MediaCodecAudioRenderer.java
│ │ │ ├── ResamplingAudioProcessor.java
│ │ │ ├── SilenceSkippingAudioProcessor.java
│ │ │ ├── SimpleDecoderAudioRenderer.java
│ │ │ ├── Sonic.java
│ │ │ ├── SonicAudioProcessor.java
│ │ │ ├── TeeAudioProcessor.java
│ │ │ ├── TrimmingAudioProcessor.java
│ │ │ ├── WavUtil.java
│ │ │ └── package-info.java
│ │ ├── database/
│ │ │ ├── DatabaseIOException.java
│ │ │ ├── DatabaseProvider.java
│ │ │ ├── DefaultDatabaseProvider.java
│ │ │ ├── ExoDatabaseProvider.java
│ │ │ ├── VersionTable.java
│ │ │ └── package-info.java
│ │ ├── decoder/
│ │ │ ├── Buffer.java
│ │ │ ├── CryptoInfo.java
│ │ │ ├── Decoder.java
│ │ │ ├── DecoderCounters.java
│ │ │ ├── DecoderInputBuffer.java
│ │ │ ├── OutputBuffer.java
│ │ │ ├── SimpleDecoder.java
│ │ │ ├── SimpleOutputBuffer.java
│ │ │ └── package-info.java
│ │ ├── drm/
│ │ │ ├── ClearKeyUtil.java
│ │ │ ├── DecryptionException.java
│ │ │ ├── DefaultDrmSession.java
│ │ │ ├── DefaultDrmSessionEventListener.java
│ │ │ ├── DefaultDrmSessionManager.java
│ │ │ ├── DrmInitData.java
│ │ │ ├── DrmSession.java
│ │ │ ├── DrmSessionManager.java
│ │ │ ├── DummyExoMediaDrm.java
│ │ │ ├── ErrorStateDrmSession.java
│ │ │ ├── ExoMediaCrypto.java
│ │ │ ├── ExoMediaDrm.java
│ │ │ ├── FrameworkMediaCrypto.java
│ │ │ ├── FrameworkMediaDrm.java
│ │ │ ├── HttpMediaDrmCallback.java
│ │ │ ├── KeysExpiredException.java
│ │ │ ├── LocalMediaDrmCallback.java
│ │ │ ├── MediaDrmCallback.java
│ │ │ ├── OfflineLicenseHelper.java
│ │ │ ├── UnsupportedDrmException.java
│ │ │ ├── WidevineUtil.java
│ │ │ └── package-info.java
│ │ ├── extractor/
│ │ │ ├── BinarySearchSeeker.java
│ │ │ ├── ChunkIndex.java
│ │ │ ├── ConstantBitrateSeekMap.java
│ │ │ ├── DefaultExtractorInput.java
│ │ │ ├── DefaultExtractorsFactory.java
│ │ │ ├── DummyExtractorOutput.java
│ │ │ ├── DummyTrackOutput.java
│ │ │ ├── Extractor.java
│ │ │ ├── ExtractorInput.java
│ │ │ ├── ExtractorOutput.java
│ │ │ ├── ExtractorsFactory.java
│ │ │ ├── GaplessInfoHolder.java
│ │ │ ├── Id3Peeker.java
│ │ │ ├── MpegAudioHeader.java
│ │ │ ├── PositionHolder.java
│ │ │ ├── SeekMap.java
│ │ │ ├── SeekPoint.java
│ │ │ ├── TrackOutput.java
│ │ │ ├── amr/
│ │ │ │ └── AmrExtractor.java
│ │ │ ├── flv/
│ │ │ │ ├── AudioTagPayloadReader.java
│ │ │ │ ├── FlvExtractor.java
│ │ │ │ ├── ScriptTagPayloadReader.java
│ │ │ │ ├── TagPayloadReader.java
│ │ │ │ └── VideoTagPayloadReader.java
│ │ │ ├── mkv/
│ │ │ │ ├── DefaultEbmlReader.java
│ │ │ │ ├── EbmlProcessor.java
│ │ │ │ ├── EbmlReader.java
│ │ │ │ ├── MatroskaExtractor.java
│ │ │ │ ├── Sniffer.java
│ │ │ │ └── VarintReader.java
│ │ │ ├── mp3/
│ │ │ │ ├── ConstantBitrateSeeker.java
│ │ │ │ ├── MlltSeeker.java
│ │ │ │ ├── Mp3Extractor.java
│ │ │ │ ├── Seeker.java
│ │ │ │ ├── VbriSeeker.java
│ │ │ │ └── XingSeeker.java
│ │ │ ├── mp4/
│ │ │ │ ├── Atom.java
│ │ │ │ ├── AtomParsers.java
│ │ │ │ ├── DefaultSampleValues.java
│ │ │ │ ├── FixedSampleSizeRechunker.java
│ │ │ │ ├── FragmentedMp4Extractor.java
│ │ │ │ ├── MdtaMetadataEntry.java
│ │ │ │ ├── MetadataUtil.java
│ │ │ │ ├── Mp4Extractor.java
│ │ │ │ ├── PsshAtomUtil.java
│ │ │ │ ├── Sniffer.java
│ │ │ │ ├── Track.java
│ │ │ │ ├── TrackEncryptionBox.java
│ │ │ │ ├── TrackFragment.java
│ │ │ │ └── TrackSampleTable.java
│ │ │ ├── ogg/
│ │ │ │ ├── DefaultOggSeeker.java
│ │ │ │ ├── FlacReader.java
│ │ │ │ ├── OggExtractor.java
│ │ │ │ ├── OggPacket.java
│ │ │ │ ├── OggPageHeader.java
│ │ │ │ ├── OggSeeker.java
│ │ │ │ ├── OpusReader.java
│ │ │ │ ├── StreamReader.java
│ │ │ │ ├── VorbisBitArray.java
│ │ │ │ ├── VorbisReader.java
│ │ │ │ └── VorbisUtil.java
│ │ │ ├── rawcc/
│ │ │ │ └── RawCcExtractor.java
│ │ │ ├── ts/
│ │ │ │ ├── Ac3Extractor.java
│ │ │ │ ├── Ac3Reader.java
│ │ │ │ ├── Ac4Extractor.java
│ │ │ │ ├── Ac4Reader.java
│ │ │ │ ├── AdtsExtractor.java
│ │ │ │ ├── AdtsReader.java
│ │ │ │ ├── DefaultTsPayloadReaderFactory.java
│ │ │ │ ├── DtsReader.java
│ │ │ │ ├── DvbSubtitleReader.java
│ │ │ │ ├── ElementaryStreamReader.java
│ │ │ │ ├── H262Reader.java
│ │ │ │ ├── H264Reader.java
│ │ │ │ ├── H265Reader.java
│ │ │ │ ├── Id3Reader.java
│ │ │ │ ├── LatmReader.java
│ │ │ │ ├── MpegAudioReader.java
│ │ │ │ ├── NalUnitTargetBuffer.java
│ │ │ │ ├── PesReader.java
│ │ │ │ ├── PsBinarySearchSeeker.java
│ │ │ │ ├── PsDurationReader.java
│ │ │ │ ├── PsExtractor.java
│ │ │ │ ├── SectionPayloadReader.java
│ │ │ │ ├── SectionReader.java
│ │ │ │ ├── SeiReader.java
│ │ │ │ ├── SpliceInfoSectionReader.java
│ │ │ │ ├── TsBinarySearchSeeker.java
│ │ │ │ ├── TsDurationReader.java
│ │ │ │ ├── TsExtractor.java
│ │ │ │ ├── TsPayloadReader.java
│ │ │ │ ├── TsUtil.java
│ │ │ │ └── UserDataReader.java
│ │ │ └── wav/
│ │ │ ├── WavExtractor.java
│ │ │ ├── WavHeader.java
│ │ │ └── WavHeaderReader.java
│ │ ├── mediacodec/
│ │ │ ├── MediaCodecInfo.java
│ │ │ ├── MediaCodecRenderer.java
│ │ │ ├── MediaCodecSelector.java
│ │ │ ├── MediaCodecUtil.java
│ │ │ ├── MediaFormatUtil.java
│ │ │ └── package-info.java
│ │ ├── metadata/
│ │ │ ├── Metadata.java
│ │ │ ├── MetadataDecoder.java
│ │ │ ├── MetadataDecoderFactory.java
│ │ │ ├── MetadataInputBuffer.java
│ │ │ ├── MetadataOutput.java
│ │ │ ├── MetadataRenderer.java
│ │ │ ├── emsg/
│ │ │ │ ├── EventMessage.java
│ │ │ │ ├── EventMessageDecoder.java
│ │ │ │ ├── EventMessageEncoder.java
│ │ │ │ └── package-info.java
│ │ │ ├── flac/
│ │ │ │ ├── PictureFrame.java
│ │ │ │ ├── VorbisComment.java
│ │ │ │ └── package-info.java
│ │ │ ├── icy/
│ │ │ │ ├── IcyDecoder.java
│ │ │ │ ├── IcyHeaders.java
│ │ │ │ ├── IcyInfo.java
│ │ │ │ └── package-info.java
│ │ │ ├── id3/
│ │ │ │ ├── ApicFrame.java
│ │ │ │ ├── BinaryFrame.java
│ │ │ │ ├── ChapterFrame.java
│ │ │ │ ├── ChapterTocFrame.java
│ │ │ │ ├── CommentFrame.java
│ │ │ │ ├── GeobFrame.java
│ │ │ │ ├── Id3Decoder.java
│ │ │ │ ├── Id3Frame.java
│ │ │ │ ├── InternalFrame.java
│ │ │ │ ├── MlltFrame.java
│ │ │ │ ├── PrivFrame.java
│ │ │ │ ├── TextInformationFrame.java
│ │ │ │ ├── UrlLinkFrame.java
│ │ │ │ └── package-info.java
│ │ │ ├── package-info.java
│ │ │ └── scte35/
│ │ │ ├── PrivateCommand.java
│ │ │ ├── SpliceCommand.java
│ │ │ ├── SpliceInfoDecoder.java
│ │ │ ├── SpliceInsertCommand.java
│ │ │ ├── SpliceNullCommand.java
│ │ │ ├── SpliceScheduleCommand.java
│ │ │ ├── TimeSignalCommand.java
│ │ │ └── package-info.java
│ │ ├── offline/
│ │ │ ├── ActionFile.java
│ │ │ ├── ActionFileUpgradeUtil.java
│ │ │ ├── DefaultDownloadIndex.java
│ │ │ ├── DefaultDownloaderFactory.java
│ │ │ ├── Download.java
│ │ │ ├── DownloadCursor.java
│ │ │ ├── DownloadException.java
│ │ │ ├── DownloadHelper.java
│ │ │ ├── DownloadIndex.java
│ │ │ ├── DownloadManager.java
│ │ │ ├── DownloadProgress.java
│ │ │ ├── DownloadRequest.java
│ │ │ ├── DownloadService.java
│ │ │ ├── Downloader.java
│ │ │ ├── DownloaderConstructorHelper.java
│ │ │ ├── DownloaderFactory.java
│ │ │ ├── FilterableManifest.java
│ │ │ ├── FilteringManifestParser.java
│ │ │ ├── ProgressiveDownloader.java
│ │ │ ├── SegmentDownloader.java
│ │ │ ├── StreamKey.java
│ │ │ ├── WritableDownloadIndex.java
│ │ │ └── package-info.java
│ │ ├── package-info.java
│ │ ├── scheduler/
│ │ │ ├── PlatformScheduler.java
│ │ │ ├── Requirements.java
│ │ │ ├── RequirementsWatcher.java
│ │ │ ├── Scheduler.java
│ │ │ └── package-info.java
│ │ ├── source/
│ │ │ ├── AbstractConcatenatedTimeline.java
│ │ │ ├── AdaptiveMediaSourceEventListener.java
│ │ │ ├── BaseMediaSource.java
│ │ │ ├── BehindLiveWindowException.java
│ │ │ ├── ClippingMediaPeriod.java
│ │ │ ├── ClippingMediaSource.java
│ │ │ ├── CompositeMediaSource.java
│ │ │ ├── CompositeSequenceableLoader.java
│ │ │ ├── CompositeSequenceableLoaderFactory.java
│ │ │ ├── ConcatenatingMediaSource.java
│ │ │ ├── DefaultCompositeSequenceableLoaderFactory.java
│ │ │ ├── DefaultMediaSourceEventListener.java
│ │ │ ├── EmptySampleStream.java
│ │ │ ├── ExtractorMediaSource.java
│ │ │ ├── ForwardingTimeline.java
│ │ │ ├── IcyDataSource.java
│ │ │ ├── LoopingMediaSource.java
│ │ │ ├── MaskingMediaPeriod.java
│ │ │ ├── MaskingMediaSource.java
│ │ │ ├── MediaPeriod.java
│ │ │ ├── MediaSource.java
│ │ │ ├── MediaSourceEventListener.java
│ │ │ ├── MediaSourceFactory.java
│ │ │ ├── MergingMediaPeriod.java
│ │ │ ├── MergingMediaSource.java
│ │ │ ├── ProgressiveMediaPeriod.java
│ │ │ ├── ProgressiveMediaSource.java
│ │ │ ├── SampleMetadataQueue.java
│ │ │ ├── SampleQueue.java
│ │ │ ├── SampleStream.java
│ │ │ ├── SequenceableLoader.java
│ │ │ ├── ShuffleOrder.java
│ │ │ ├── SilenceMediaSource.java
│ │ │ ├── SinglePeriodTimeline.java
│ │ │ ├── SingleSampleMediaPeriod.java
│ │ │ ├── SingleSampleMediaSource.java
│ │ │ ├── TrackGroup.java
│ │ │ ├── TrackGroupArray.java
│ │ │ ├── UnrecognizedInputFormatException.java
│ │ │ ├── ads/
│ │ │ │ ├── AdPlaybackState.java
│ │ │ │ ├── AdsLoader.java
│ │ │ │ ├── AdsMediaSource.java
│ │ │ │ └── SinglePeriodAdTimeline.java
│ │ │ ├── chunk/
│ │ │ │ ├── BaseMediaChunk.java
│ │ │ │ ├── BaseMediaChunkIterator.java
│ │ │ │ ├── BaseMediaChunkOutput.java
│ │ │ │ ├── Chunk.java
│ │ │ │ ├── ChunkExtractorWrapper.java
│ │ │ │ ├── ChunkHolder.java
│ │ │ │ ├── ChunkSampleStream.java
│ │ │ │ ├── ChunkSource.java
│ │ │ │ ├── ContainerMediaChunk.java
│ │ │ │ ├── DataChunk.java
│ │ │ │ ├── InitializationChunk.java
│ │ │ │ ├── MediaChunk.java
│ │ │ │ ├── MediaChunkIterator.java
│ │ │ │ ├── MediaChunkListIterator.java
│ │ │ │ └── SingleSampleMediaChunk.java
│ │ │ ├── dash/
│ │ │ │ ├── DashChunkSource.java
│ │ │ │ ├── DashManifestStaleException.java
│ │ │ │ ├── DashMediaPeriod.java
│ │ │ │ ├── DashMediaSource.java
│ │ │ │ ├── DashSegmentIndex.java
│ │ │ │ ├── DashUtil.java
│ │ │ │ ├── DashWrappingSegmentIndex.java
│ │ │ │ ├── DefaultDashChunkSource.java
│ │ │ │ ├── EventSampleStream.java
│ │ │ │ ├── PlayerEmsgHandler.java
│ │ │ │ ├── manifest/
│ │ │ │ │ ├── AdaptationSet.java
│ │ │ │ │ ├── DashManifest.java
│ │ │ │ │ ├── DashManifestParser.java
│ │ │ │ │ ├── Descriptor.java
│ │ │ │ │ ├── EventStream.java
│ │ │ │ │ ├── Period.java
│ │ │ │ │ ├── ProgramInformation.java
│ │ │ │ │ ├── RangedUri.java
│ │ │ │ │ ├── Representation.java
│ │ │ │ │ ├── SegmentBase.java
│ │ │ │ │ ├── SingleSegmentIndex.java
│ │ │ │ │ ├── UrlTemplate.java
│ │ │ │ │ ├── UtcTimingElement.java
│ │ │ │ │ └── package-info.java
│ │ │ │ ├── offline/
│ │ │ │ │ ├── DashDownloader.java
│ │ │ │ │ └── package-info.java
│ │ │ │ └── package-info.java
│ │ │ ├── hls/
│ │ │ │ ├── Aes128DataSource.java
│ │ │ │ ├── DefaultHlsDataSourceFactory.java
│ │ │ │ ├── DefaultHlsExtractorFactory.java
│ │ │ │ ├── FullSegmentEncryptionKeyCache.java
│ │ │ │ ├── HlsChunkSource.java
│ │ │ │ ├── HlsDataSourceFactory.java
│ │ │ │ ├── HlsExtractorFactory.java
│ │ │ │ ├── HlsManifest.java
│ │ │ │ ├── HlsMediaChunk.java
│ │ │ │ ├── HlsMediaPeriod.java
│ │ │ │ ├── HlsMediaSource.java
│ │ │ │ ├── HlsSampleStream.java
│ │ │ │ ├── HlsSampleStreamWrapper.java
│ │ │ │ ├── HlsTrackMetadataEntry.java
│ │ │ │ ├── SampleQueueMappingException.java
│ │ │ │ ├── TimestampAdjusterProvider.java
│ │ │ │ ├── WebvttExtractor.java
│ │ │ │ ├── offline/
│ │ │ │ │ ├── HlsDownloader.java
│ │ │ │ │ └── package-info.java
│ │ │ │ ├── package-info.java
│ │ │ │ └── playlist/
│ │ │ │ ├── DefaultHlsPlaylistParserFactory.java
│ │ │ │ ├── DefaultHlsPlaylistTracker.java
│ │ │ │ ├── FilteringHlsPlaylistParserFactory.java
│ │ │ │ ├── HlsMasterPlaylist.java
│ │ │ │ ├── HlsMediaPlaylist.java
│ │ │ │ ├── HlsPlaylist.java
│ │ │ │ ├── HlsPlaylistParser.java
│ │ │ │ ├── HlsPlaylistParserFactory.java
│ │ │ │ ├── HlsPlaylistTracker.java
│ │ │ │ └── package-info.java
│ │ │ └── smoothstreaming/
│ │ │ ├── DefaultSsChunkSource.java
│ │ │ ├── SsChunkSource.java
│ │ │ ├── SsMediaPeriod.java
│ │ │ ├── SsMediaSource.java
│ │ │ ├── manifest/
│ │ │ │ ├── SsManifest.java
│ │ │ │ ├── SsManifestParser.java
│ │ │ │ ├── SsUtil.java
│ │ │ │ └── package-info.java
│ │ │ ├── offline/
│ │ │ │ ├── SsDownloader.java
│ │ │ │ └── package-info.java
│ │ │ └── package-info.java
│ │ ├── text/
│ │ │ ├── CaptionStyleCompat.java
│ │ │ ├── Cue.java
│ │ │ ├── SimpleSubtitleDecoder.java
│ │ │ ├── SimpleSubtitleOutputBuffer.java
│ │ │ ├── Subtitle.java
│ │ │ ├── SubtitleDecoder.java
│ │ │ ├── SubtitleDecoderException.java
│ │ │ ├── SubtitleDecoderFactory.java
│ │ │ ├── SubtitleInputBuffer.java
│ │ │ ├── SubtitleOutputBuffer.java
│ │ │ ├── TextOutput.java
│ │ │ ├── TextRenderer.java
│ │ │ ├── cea/
│ │ │ │ ├── Cea608Decoder.java
│ │ │ │ ├── Cea708Cue.java
│ │ │ │ ├── Cea708Decoder.java
│ │ │ │ ├── Cea708InitializationData.java
│ │ │ │ ├── CeaDecoder.java
│ │ │ │ ├── CeaSubtitle.java
│ │ │ │ ├── CeaUtil.java
│ │ │ │ └── package-info.java
│ │ │ ├── dvb/
│ │ │ │ ├── DvbDecoder.java
│ │ │ │ ├── DvbParser.java
│ │ │ │ ├── DvbSubtitle.java
│ │ │ │ └── package-info.java
│ │ │ ├── package-info.java
│ │ │ ├── pgs/
│ │ │ │ ├── PgsDecoder.java
│ │ │ │ ├── PgsSubtitle.java
│ │ │ │ └── package-info.java
│ │ │ ├── ssa/
│ │ │ │ ├── SsaDecoder.java
│ │ │ │ ├── SsaDialogueFormat.java
│ │ │ │ ├── SsaStyle.java
│ │ │ │ ├── SsaSubtitle.java
│ │ │ │ └── package-info.java
│ │ │ ├── subrip/
│ │ │ │ ├── SubripDecoder.java
│ │ │ │ ├── SubripSubtitle.java
│ │ │ │ └── package-info.java
│ │ │ ├── ttml/
│ │ │ │ ├── TtmlDecoder.java
│ │ │ │ ├── TtmlNode.java
│ │ │ │ ├── TtmlRegion.java
│ │ │ │ ├── TtmlRenderUtil.java
│ │ │ │ ├── TtmlStyle.java
│ │ │ │ ├── TtmlSubtitle.java
│ │ │ │ └── package-info.java
│ │ │ ├── tx3g/
│ │ │ │ ├── Tx3gDecoder.java
│ │ │ │ ├── Tx3gSubtitle.java
│ │ │ │ └── package-info.java
│ │ │ └── webvtt/
│ │ │ ├── CssParser.java
│ │ │ ├── Mp4WebvttDecoder.java
│ │ │ ├── Mp4WebvttSubtitle.java
│ │ │ ├── WebvttCssStyle.java
│ │ │ ├── WebvttCue.java
│ │ │ ├── WebvttCueParser.java
│ │ │ ├── WebvttDecoder.java
│ │ │ ├── WebvttParserUtil.java
│ │ │ ├── WebvttSubtitle.java
│ │ │ └── package-info.java
│ │ ├── trackselection/
│ │ │ ├── AdaptiveTrackSelection.java
│ │ │ ├── BaseTrackSelection.java
│ │ │ ├── BufferSizeAdaptationBuilder.java
│ │ │ ├── DefaultTrackSelector.java
│ │ │ ├── FixedTrackSelection.java
│ │ │ ├── MappingTrackSelector.java
│ │ │ ├── RandomTrackSelection.java
│ │ │ ├── TrackSelection.java
│ │ │ ├── TrackSelectionArray.java
│ │ │ ├── TrackSelectionParameters.java
│ │ │ ├── TrackSelectionUtil.java
│ │ │ ├── TrackSelector.java
│ │ │ ├── TrackSelectorResult.java
│ │ │ └── package-info.java
│ │ ├── upstream/
│ │ │ ├── Allocation.java
│ │ │ ├── Allocator.java
│ │ │ ├── AssetDataSource.java
│ │ │ ├── BandwidthMeter.java
│ │ │ ├── BaseDataSource.java
│ │ │ ├── ByteArrayDataSink.java
│ │ │ ├── ByteArrayDataSource.java
│ │ │ ├── ContentDataSource.java
│ │ │ ├── DataSchemeDataSource.java
│ │ │ ├── DataSink.java
│ │ │ ├── DataSource.java
│ │ │ ├── DataSourceException.java
│ │ │ ├── DataSourceInputStream.java
│ │ │ ├── DataSpec.java
│ │ │ ├── DefaultAllocator.java
│ │ │ ├── DefaultBandwidthMeter.java
│ │ │ ├── DefaultDataSource.java
│ │ │ ├── DefaultDataSourceFactory.java
│ │ │ ├── DefaultHttpDataSource.java
│ │ │ ├── DefaultHttpDataSourceFactory.java
│ │ │ ├── DefaultLoadErrorHandlingPolicy.java
│ │ │ ├── DummyDataSource.java
│ │ │ ├── FileDataSource.java
│ │ │ ├── FileDataSourceFactory.java
│ │ │ ├── HttpDataSource.java
│ │ │ ├── LoadErrorHandlingPolicy.java
│ │ │ ├── Loader.java
│ │ │ ├── LoaderErrorThrower.java
│ │ │ ├── ParsingLoadable.java
│ │ │ ├── PriorityDataSource.java
│ │ │ ├── PriorityDataSourceFactory.java
│ │ │ ├── RawResourceDataSource.java
│ │ │ ├── ResolvingDataSource.java
│ │ │ ├── StatsDataSource.java
│ │ │ ├── TeeDataSource.java
│ │ │ ├── TransferListener.java
│ │ │ ├── UdpDataSource.java
│ │ │ ├── cache/
│ │ │ │ ├── Cache.java
│ │ │ │ ├── CacheDataSink.java
│ │ │ │ ├── CacheDataSinkFactory.java
│ │ │ │ ├── CacheDataSource.java
│ │ │ │ ├── CacheDataSourceFactory.java
│ │ │ │ ├── CacheEvictor.java
│ │ │ │ ├── CacheFileMetadata.java
│ │ │ │ ├── CacheFileMetadataIndex.java
│ │ │ │ ├── CacheKeyFactory.java
│ │ │ │ ├── CacheSpan.java
│ │ │ │ ├── CacheUtil.java
│ │ │ │ ├── CachedContent.java
│ │ │ │ ├── CachedContentIndex.java
│ │ │ │ ├── CachedRegionTracker.java
│ │ │ │ ├── ContentMetadata.java
│ │ │ │ ├── ContentMetadataMutations.java
│ │ │ │ ├── DefaultContentMetadata.java
│ │ │ │ ├── LeastRecentlyUsedCacheEvictor.java
│ │ │ │ ├── NoOpCacheEvictor.java
│ │ │ │ ├── SimpleCache.java
│ │ │ │ └── SimpleCacheSpan.java
│ │ │ └── crypto/
│ │ │ ├── AesCipherDataSink.java
│ │ │ ├── AesCipherDataSource.java
│ │ │ ├── AesFlushingCipher.java
│ │ │ └── CryptoUtil.java
│ │ ├── util/
│ │ │ ├── Assertions.java
│ │ │ ├── AtomicFile.java
│ │ │ ├── Clock.java
│ │ │ ├── CodecSpecificDataUtil.java
│ │ │ ├── ColorParser.java
│ │ │ ├── ConditionVariable.java
│ │ │ ├── EGLSurfaceTexture.java
│ │ │ ├── ErrorMessageProvider.java
│ │ │ ├── EventDispatcher.java
│ │ │ ├── EventLogger.java
│ │ │ ├── FlacStreamMetadata.java
│ │ │ ├── GlUtil.java
│ │ │ ├── HandlerWrapper.java
│ │ │ ├── LibraryLoader.java
│ │ │ ├── Log.java
│ │ │ ├── LongArray.java
│ │ │ ├── MediaClock.java
│ │ │ ├── MimeTypes.java
│ │ │ ├── NalUnitUtil.java
│ │ │ ├── NonNullApi.java
│ │ │ ├── NotificationUtil.java
│ │ │ ├── ParsableBitArray.java
│ │ │ ├── ParsableByteArray.java
│ │ │ ├── ParsableNalUnitBitArray.java
│ │ │ ├── Predicate.java
│ │ │ ├── PriorityTaskManager.java
│ │ │ ├── RepeatModeUtil.java
│ │ │ ├── ReusableBufferedOutputStream.java
│ │ │ ├── SlidingPercentile.java
│ │ │ ├── StandaloneMediaClock.java
│ │ │ ├── SystemClock.java
│ │ │ ├── SystemHandlerWrapper.java
│ │ │ ├── TimedValueQueue.java
│ │ │ ├── TimestampAdjuster.java
│ │ │ ├── TraceUtil.java
│ │ │ ├── UriUtil.java
│ │ │ ├── Util.java
│ │ │ ├── XmlPullParserUtil.java
│ │ │ └── package-info.java
│ │ └── video/
│ │ ├── AvcConfig.java
│ │ ├── ColorInfo.java
│ │ ├── DolbyVisionConfig.java
│ │ ├── DummySurface.java
│ │ ├── HevcConfig.java
│ │ ├── MediaCodecVideoRenderer.java
│ │ ├── SimpleDecoderVideoRenderer.java
│ │ ├── VideoDecoderException.java
│ │ ├── VideoDecoderGLSurfaceView.java
│ │ ├── VideoDecoderInputBuffer.java
│ │ ├── VideoDecoderOutputBuffer.java
│ │ ├── VideoDecoderOutputBufferRenderer.java
│ │ ├── VideoDecoderRenderer.java
│ │ ├── VideoFrameMetadataListener.java
│ │ ├── VideoFrameReleaseTimeHelper.java
│ │ ├── VideoListener.java
│ │ ├── VideoRendererEventListener.java
│ │ ├── package-info.java
│ │ └── spherical/
│ │ ├── CameraMotionListener.java
│ │ ├── CameraMotionRenderer.java
│ │ ├── FrameRotationQueue.java
│ │ ├── Projection.java
│ │ ├── ProjectionDecoder.java
│ │ └── package-info.java
│ └── res/
│ └── values/
│ └── strings.xml
├── gradle/
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── ijkplayer/
│ ├── .gitignore
│ ├── build.gradle
│ ├── ijkplayer.iml
│ ├── proguard-rules.pro
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ ├── java/
│ │ └── tv/
│ │ └── danmaku/
│ │ └── ijk/
│ │ └── media/
│ │ └── player/
│ │ ├── AbstractMediaPlayer.java
│ │ ├── AndroidMediaPlayer.java
│ │ ├── IMediaPlayer.java
│ │ ├── ISurfaceTextureHolder.java
│ │ ├── ISurfaceTextureHost.java
│ │ ├── IjkLibLoader.java
│ │ ├── IjkMediaCodecInfo.java
│ │ ├── IjkMediaMeta.java
│ │ ├── IjkMediaPlayer.java
│ │ ├── IjkTimedText.java
│ │ ├── MediaInfo.java
│ │ ├── MediaPlayerProxy.java
│ │ ├── TextureMediaPlayer.java
│ │ ├── annotations/
│ │ │ ├── AccessedByNative.java
│ │ │ └── CalledByNative.java
│ │ ├── exceptions/
│ │ │ └── IjkMediaException.java
│ │ ├── ffmpeg/
│ │ │ └── FFmpegApi.java
│ │ ├── misc/
│ │ │ ├── AndroidMediaFormat.java
│ │ │ ├── AndroidTrackInfo.java
│ │ │ ├── IAndroidIO.java
│ │ │ ├── IMediaDataSource.java
│ │ │ ├── IMediaFormat.java
│ │ │ ├── ITrackInfo.java
│ │ │ ├── IjkMediaFormat.java
│ │ │ └── IjkTrackInfo.java
│ │ └── pragma/
│ │ ├── DebugLog.java
│ │ └── Pragma.java
│ └── res/
│ └── values/
│ └── strings.xml
├── mediaproxy/
│ ├── .gitignore
│ ├── build.gradle
│ ├── mediaproxy.iml
│ ├── proguard-rules.pro
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ ├── java/
│ │ └── com/
│ │ └── media/
│ │ └── cache/
│ │ ├── CacheManager.java
│ │ ├── DownloadConstants.java
│ │ ├── LocalProxyConfig.java
│ │ ├── StorageManager.java
│ │ ├── VideoCacheException.java
│ │ ├── VideoDownloadManager.java
│ │ ├── VideoDownloadQueue.java
│ │ ├── VideoInfoParserManager.java
│ │ ├── download/
│ │ │ ├── BaseVideoDownloadTask.java
│ │ │ ├── M3U8VideoDownloadTask.java
│ │ │ └── VideoDownloadTask.java
│ │ ├── hls/
│ │ │ ├── M3U8.java
│ │ │ ├── M3U8Constants.java
│ │ │ ├── M3U8Ts.java
│ │ │ └── M3U8Utils.java
│ │ ├── http/
│ │ │ ├── ChunkedOutputStream.java
│ │ │ ├── ContentType.java
│ │ │ ├── HttpRequest.java
│ │ │ ├── HttpResponse.java
│ │ │ ├── IState.java
│ │ │ ├── Method.java
│ │ │ ├── ResponseState.java
│ │ │ └── SocketProcessorTask.java
│ │ ├── listener/
│ │ │ ├── IDownloadInfosCallback.java
│ │ │ ├── IDownloadListener.java
│ │ │ ├── IDownloadTaskListener.java
│ │ │ ├── IVideoInfoCallback.java
│ │ │ └── IVideoInfoParseCallback.java
│ │ ├── model/
│ │ │ ├── Video.java
│ │ │ ├── VideoCacheInfo.java
│ │ │ ├── VideoTaskItem.java
│ │ │ ├── VideoTaskMode.java
│ │ │ └── VideoTaskState.java
│ │ ├── proxy/
│ │ │ ├── AsyncProxyServer.java
│ │ │ └── CustomProxyServer.java
│ │ └── utils/
│ │ ├── DownloadExceptionUtils.java
│ │ ├── HttpUtils.java
│ │ ├── LocalProxyThreadUtils.java
│ │ ├── LocalProxyUtils.java
│ │ └── StorageUtils.java
│ └── res/
│ └── values/
│ └── strings.xml
├── playersdk/
│ ├── .gitignore
│ ├── build.gradle
│ ├── playersdk.iml
│ ├── proguard-rules.pro
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ ├── java/
│ │ └── com/
│ │ └── android/
│ │ └── player/
│ │ ├── CommonPlayer.java
│ │ ├── IPlayer.java
│ │ ├── PlayerAttributes.java
│ │ ├── PlayerType.java
│ │ ├── impl/
│ │ │ ├── ExoPlayerImpl.java
│ │ │ ├── IjkPlayerImpl.java
│ │ │ ├── MediaPlayerImpl.java
│ │ │ └── PlayerImpl.java
│ │ └── proxy/
│ │ └── LocalProxyPlayerImpl.java
│ └── res/
│ └── values/
│ └── strings.xml
└── settings.gradle
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
build
.gradle
.idea
local.properties
app/app.iml
playerlib/playerlib.iml
exoplayerlib/exoplayerlib.iml
ijkplayerlib/ijkplayerlib.iml
mediaproxylib/mediaproxylib.iml
mediaproxylib/build
mediaproxylib/build/*
/MediaSDK.iml
================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2014-2016 Alexey Danilov
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: README.md
================================================
# MediaSDK
The library is working for downloading video while playing the video, the video contains M3U8/MP4
Developer documentation is here, [Click it](./README_cn.md)
You can refer to the technical documentation:https://www.jianshu.com/p/27085da32a35
Use the library:
```
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
dependencies {
implementation 'com.github.JeffMony:MediaSDK:2.0.0'
}
```
#### The Core functions of the project
> * Cache management, LRU
> * Video downloading management
> * Local proxy management
> * Display downloading speed
> * Display the video cache's size
> * Support the video's downloading while playing the video, M3U8/MP4 video
The project's architecture is as follows:

### Developer documentation
#### 1.Application->onCreate(...)
```
File file = LocalProxyUtils.getVideoCacheDir(this);
if (!file.exists()) {
file.mkdir();
}
LocalProxyConfig config = new VideoDownloadManager.Build(this)
.setCacheRoot(file)
.setUrlRedirect(false)
.setTimeOut(DownloadConstants.READ_TIMEOUT, DownloadConstants.CONN_TIMEOUT, DownloadConstants.SOCKET_TIMEOUT)
.setConcurrentCount(DownloadConstants.CONCURRENT_COUNT)
.setIgnoreAllCertErrors(true)
.buildConfig();
VideoDownloadManager.getInstance().initConfig(config);
```
1.setCacheRoot The cache path;
2.setUrlRedirect Support request's redirect;
3.setCacheSize Set cache size;
4.setTimeOut Set request's timeout;
5.setPort Set the local proxy server's port;
6.setIgnoreAllCertErrors Support the certificate;
#### 2.The local proxy server's switch
```
PlayerAttributes attributes = new PlayerAttributes();
attributes.setUseLocalProxy(mUseLocalProxy);
```
#### 3.Set the listener
```
mPlayer.setOnLocalProxyCacheListener(mOnLocalProxyCacheListener);
mPlayer.startLocalProxy(mUrl, null);
private IPlayer.OnLocalProxyCacheListener mOnLocalProxyCacheListener = new IPlayer.OnLocalProxyCacheListener() {
@Override
public void onCacheReady(IPlayer mp, String proxyUrl) {
LogUtils.w("onCacheReady proxyUrl = " + proxyUrl);
Uri uri = Uri.parse(proxyUrl);
try {
mPlayer.setDataSource(PlayerActivity.this, uri);
} catch (IOException e) {
e.printStackTrace();
return;
}
mPlayer.setSurface(mSurface);
mPlayer.setOnPreparedListener(mPreparedListener);
mPlayer.setOnVideoSizeChangedListener(mVideoSizeChangeListener);
mPlayer.prepareAsync();
}
@Override
public void onCacheProgressChanged(IPlayer mp, int percent, long cachedSize) {
LogUtils.w("onCacheProgressChanged percent = " + percent);
mPercent = percent;
}
@Override
public void onCacheSpeedChanged(String url, float cacheSpeed) {
if (mPlayer != null && mPlayer.get() != null) {
mPlayer.get().notifyProxyCacheSpeed(cacheSpeed);
}
}
@Override
public void onCacheFinished(String url) {
LogUtils.i("onCacheFinished url="+url + ", player="+this);
mIsCompleteCached = true;
}
@Override
public void onCacheForbidden(String url) {
LogUtils.w("onCacheForbidden url="+url+", player="+this);
mUseLocalProxy = false;
if (mPlayer != null && mPlayer.get() != null) {
mPlayer.get().notifyProxyCacheForbidden(url);
}
}
@Override
public void onCacheFailed(String url, Exception e) {
LogUtils.w("onCacheFailed , player="+this);
pauseProxyCacheTask(PROXY_CACHE_EXCEPTION);
}
};
```
demo:



================================================
FILE: README_cn.md
================================================
# MediaSDK
关注一下分析文章:https://www.jianshu.com/p/27085da32a35
```
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
dependencies {
implementation 'com.github.JeffMony:MediaSDK:2.0.0'
}
```
最近这个项目有新的维护计划:
> * 1.本地代理的控制逻辑移到server端
> * 2.增加mp4 moov端的识别规则
> * 3.将本地代理库和播放器解耦
### 版本LOG
2.0.0
> * 1.使用androidasync替换proxyserver
> * 2.优化MediaSDK接口
t1.5.0
> * 1.视频下载队列,可以设置视频并发下载的个数
> * 2.视频播放缓存和下载缓存的数据合并,但是逻辑分离
t1.4.0
> * 1.增加视频下载模块;
> * 2.重构本地代理模块代码;
> * 3.视频下载和本地代理模块代码复用;
> * 4.还有一些bug待处理,很快更新
> * 5.后续版本更新计划: 下载队列;初始化本地已下载的视频;下载和播放缓存隔离;
t1.3.0
> * 1.封装好边下边播模块
> * 2.可以直接商用
v1.1.0
> * 1.解决https 证书出错的视频url请求,信任证书;
> * 2.解决播放过程中息屏的问题,保持屏幕常亮;
> * 3.增加 isPlaying 接口,表示当前是否正在播放视频;
> * 4.解决Cleartext HTTP traffic to 127.0.0.1 not permitted 问题,Android P版本不允许未加密请求;
v1.0.0
> * 1.支持MediaPlayer/IjkPlayer/ExoPlayer 三种播放器播放视频;
> * 2.支持M3U8/MP4视频的边下边播功能;
> * 3.本地代理实现边下边播功能;
> * 4.提供当前下载速度和下载进度的回调;
#### 封装了一个播放器功能库
> * 实现ijkplayer exoplayer mediaplayer 3种播放器类型;可以任意切换;
> * ijkplayer 是从 k0.8.8分支上拉出来的;
> * exoplayer 是 2.11.1版本
#### 实现视频边下边播的功能
> * 缓存管理
> * 下载管理
> * 本地代理管理模块(使用androidasync管理本地代理)
> * 回调播放下载实时速度
> * 显示缓存大小
本项目的架构如下:

从上面的架构可以看出来,本项目的重点在本地代理层,这是实现边下边播的核心逻辑;
### 接入库须知
#### 1.在Application->onCreate(...) 中初始化
```
File file = LocalProxyUtils.getVideoCacheDir(this);
if (!file.exists()) {
file.mkdir();
}
LocalProxyConfig config = new VideoDownloadManager.Build(this)
.setCacheRoot(file)
.setUrlRedirect(false)
.setTimeOut(DownloadConstants.READ_TIMEOUT, DownloadConstants.CONN_TIMEOUT, DownloadConstants.SOCKET_TIMEOUT)
.setConcurrentCount(DownloadConstants.CONCURRENT_COUNT)
.setIgnoreAllCertErrors(true)
.buildConfig();
VideoDownloadManager.getInstance().initConfig(config);
```
这儿可以设置一些属性:
1.setCacheRoot 设置缓存的路径;
2.setUrlRedirect 是否需要重定向请求;
3.setCacheSize 设置缓存的大小限制;
4.setTimeOut 设置连接和读超时时间;
5.setPort 设置本地代理的端口;
6.setIgnoreAllCertErrors 是否需要信任证书;
#### 2.打开本地代理开关
```
PlayerAttributes attributes = new PlayerAttributes();
attributes.setUseLocalProxy(mUseLocalProxy);
```
#### 3.设置本地代理模块监听
```
mPlayer.setOnLocalProxyCacheListener(mOnLocalProxyCacheListener);
mPlayer.startLocalProxy(mUrl, null);
private IPlayer.OnLocalProxyCacheListener mOnLocalProxyCacheListener = new IPlayer.OnLocalProxyCacheListener() {
@Override
public void onCacheReady(IPlayer mp, String proxyUrl) {
LogUtils.w("onCacheReady proxyUrl = " + proxyUrl);
Uri uri = Uri.parse(proxyUrl);
try {
mPlayer.setDataSource(PlayerActivity.this, uri);
} catch (IOException e) {
e.printStackTrace();
return;
}
mPlayer.setSurface(mSurface);
mPlayer.setOnPreparedListener(mPreparedListener);
mPlayer.setOnVideoSizeChangedListener(mVideoSizeChangeListener);
mPlayer.prepareAsync();
}
@Override
public void onCacheProgressChanged(IPlayer mp, int percent, long cachedSize) {
LogUtils.w("onCacheProgressChanged percent = " + percent);
mPercent = percent;
}
@Override
public void onCacheSpeedChanged(String url, float cacheSpeed) {
if (mPlayer != null && mPlayer.get() != null) {
mPlayer.get().notifyProxyCacheSpeed(cacheSpeed);
}
}
@Override
public void onCacheFinished(String url) {
LogUtils.i("onCacheFinished url="+url + ", player="+this);
mIsCompleteCached = true;
}
@Override
public void onCacheForbidden(String url) {
LogUtils.w("onCacheForbidden url="+url+", player="+this);
mUseLocalProxy = false;
if (mPlayer != null && mPlayer.get() != null) {
mPlayer.get().notifyProxyCacheForbidden(url);
}
}
@Override
public void onCacheFailed(String url, Exception e) {
LogUtils.w("onCacheFailed , player="+this);
pauseProxyCacheTask(PROXY_CACHE_EXCEPTION);
}
};
```
#### 4.本地代理的生命周期跟着播放器的生命周期一起
#### 5.下载接入函数
```
public interface IDownloadListener {
void onDownloadPrepare(VideoTaskItem item);
void onDownloadPending(VideoTaskItem item);
void onDownloadStart(VideoTaskItem item);
void onDownloadProxyReady(VideoTaskItem item);
void onDownloadProgress(VideoTaskItem item);
void onDownloadSpeed(VideoTaskItem item);
void onDownloadPause(VideoTaskItem item);
void onDownloadError(VideoTaskItem item);
void onDownloadProxyForbidden(VideoTaskItem item);
void onDownloadSuccess(VideoTaskItem item);
}
```
### 功能概要
#### 1.封装了一个player sdk层
> * 1.1 接入Android 原生的 MediaPlayer 播放器
> * 1.2 接入google的EXO player 播放器
> * 1.3 接入开源的 ijk player 播放器
#### 2.实现整视频的边下边播
> * 2.1 实现整视频的分片下载
> * 2.2 实现整视频的断点下载
#### 3.实现HLS分片视频的边下边播
> * 3.1 实现HLS视频源的解析工作
> * 3.2 实现HLS的边下边播
> * 3.3 实现HLS的断点下载功能
#### 4.线程池控制下载功能
#### 5.提供视频下载的额外功能
> * 5.1 可以提供播放视频或者下载视频的实时网速
> * 5.2 可以提供已缓存视频的大小
demo示意图:

欢迎关注我的公众号JeffMony,我会持续为你带来音视频---算法---Android---python 方面的知识分享

如果你觉得这个库有用,请鼓励一下吧;

================================================
FILE: androidasync/.gitignore
================================================
/build
================================================
FILE: androidasync/androidasync.iml
================================================
generateDebugSources
================================================
FILE: androidasync/build.gradle
================================================
apply plugin: 'com.android.library'
android {
compileSdkVersion 29
buildToolsVersion "29.0.3"
defaultConfig {
minSdkVersion 19
targetSdkVersion 29
versionCode 1
versionName "1.0"
consumerProguardFiles 'consumer-rules.pro'
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
compileOnly group: 'org.bouncycastle', name: 'bcprov-jdk15on', version: '1.60'
compileOnly group: 'org.bouncycastle', name: 'bcpkix-jdk15on', version: '1.60'
implementation 'androidx.appcompat:appcompat:1.1.0'
}
================================================
FILE: androidasync/consumer-rules.pro
================================================
================================================
FILE: androidasync/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
================================================
FILE: androidasync/src/main/AndroidManifest.xml
================================================
================================================
FILE: androidasync/src/main/java/com/jeffmony/async/AsyncDatagramSocket.java
================================================
package com.jeffmony.async;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
public class AsyncDatagramSocket extends AsyncNetworkSocket {
public void disconnect() throws IOException {
socketAddress = null;
((DatagramChannelWrapper)getChannel()).disconnect();
}
@Override
public InetSocketAddress getRemoteAddress() {
if (isOpen())
return super.getRemoteAddress();
return ((DatagramChannelWrapper)getChannel()).getRemoteAddress();
}
public void connect(InetSocketAddress address) throws IOException {
socketAddress = address;
((DatagramChannelWrapper)getChannel()).mChannel.connect(address);
}
public void send(final String host, final int port, final ByteBuffer buffer) {
if (getServer().getAffinity() != Thread.currentThread()) {
getServer().run(new Runnable() {
@Override
public void run() {
send(host, port, buffer);
}
});
return;
}
try {
((DatagramChannelWrapper)getChannel()).mChannel.send(buffer, new InetSocketAddress(host, port));
}
catch (IOException e) {
// close();
// reportEndPending(e);
// reportClose(e);
}
}
public void send(final InetSocketAddress address, final ByteBuffer buffer) {
if (getServer().getAffinity() != Thread.currentThread()) {
getServer().run(new Runnable() {
@Override
public void run() {
send(address, buffer);
}
});
return;
}
try {
int sent = ((DatagramChannelWrapper)getChannel()).mChannel.send(buffer, new InetSocketAddress(address.getHostName(), address.getPort()));
}
catch (IOException e) {
// Log.e("SEND", "send error", e);
// close();
// reportEndPending(e);
// reportClose(e);
}
}
}
================================================
FILE: androidasync/src/main/java/com/jeffmony/async/AsyncNetworkSocket.java
================================================
package com.jeffmony.async;
import android.util.Log;
import com.jeffmony.async.callback.CompletedCallback;
import com.jeffmony.async.callback.DataCallback;
import com.jeffmony.async.callback.WritableCallback;
import com.jeffmony.async.util.Allocator;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
public class AsyncNetworkSocket implements AsyncSocket {
AsyncNetworkSocket() {
}
@Override
public void end() {
mChannel.shutdownOutput();
}
public boolean isChunked() {
return mChannel.isChunked();
}
InetSocketAddress socketAddress;
void attach(SocketChannel channel, InetSocketAddress socketAddress) throws IOException {
this.socketAddress = socketAddress;
allocator = new Allocator();
mChannel = new SocketChannelWrapper(channel);
}
void attach(DatagramChannel channel) throws IOException {
mChannel = new DatagramChannelWrapper(channel);
// keep udp at roughly the mtu, which is 1540 or something
// letting it grow freaks out nio apparently.
allocator = new Allocator(8192);
}
ChannelWrapper getChannel() {
return mChannel;
}
public void onDataWritable() {
// assert mWriteableHandler != null;
if (!mChannel.isChunked()) {
// turn write off
mKey.interestOps(~SelectionKey.OP_WRITE & mKey.interestOps());
}
if (mWriteableHandler != null)
mWriteableHandler.onWriteable();
}
private ChannelWrapper mChannel;
private SelectionKey mKey;
private AsyncServer mServer;
void setup(AsyncServer server, SelectionKey key) {
mServer = server;
mKey = key;
}
@Override
public void write(final ByteBufferList list) {
if (mServer.getAffinity() != Thread.currentThread()) {
mServer.run(new Runnable() {
@Override
public void run() {
write(list);
}
});
return;
}
if (!mChannel.isConnected()) {
assert !mChannel.isChunked();
return;
}
try {
int before = list.remaining();
ByteBuffer[] arr = list.getAllArray();
mChannel.write(arr);
list.addAll(arr);
handleRemaining(list.remaining());
mServer.onDataSent(before - list.remaining());
}
catch (IOException e) {
closeInternal();
reportEndPending(e);
reportClose(e);
}
}
private void handleRemaining(int remaining) throws IOException {
if (!mKey.isValid())
throw new IOException(new CancelledKeyException());
if (remaining > 0) {
// chunked channels should not fail
assert !mChannel.isChunked();
// register for a write notification if a write fails
// turn write on
mKey.interestOps(SelectionKey.OP_WRITE | mKey.interestOps());
}
else {
// turn write off
mKey.interestOps(~SelectionKey.OP_WRITE & mKey.interestOps());
}
}
private ByteBufferList pending = new ByteBufferList();
// private ByteBuffer[] buffers = new ByteBuffer[8];
Allocator allocator;
int onReadable() {
spitPending();
// even if the socket is paused,
// it may end up getting a queued readable event if it is
// already in the selector's ready queue.
if (mPaused)
return 0;
int total = 0;
boolean closed = false;
// ByteBufferList.obtainArray(buffers, Math.min(Math.max(mToAlloc, 2 << 11), maxAlloc));
ByteBuffer b = allocator.allocate();
// keep track of the max mount read during this read cycle
// so we can be quicker about allocations during the next
// time this socket reads.
long read;
try {
read = mChannel.read(b);
}
catch (Exception e) {
read = -1;
closeInternal();
reportEndPending(e);
reportClose(e);
}
if (read < 0) {
closeInternal();
closed = true;
}
else {
total += read;
}
if (read > 0) {
allocator.track(read);
b.flip();
// for (int i = 0; i < buffers.length; i++) {
// ByteBuffer b = buffers[i];
// buffers[i] = null;
// b.flip();
// pending.add(b);
// }
pending.add(b);
Util.emitAllData(this, pending);
}
else {
ByteBufferList.reclaim(b);
}
if (closed) {
reportEndPending(null);
reportClose(null);
}
return total;
}
boolean closeReported;
protected void reportClose(Exception e) {
if (closeReported)
return;
closeReported = true;
if (mClosedHander != null) {
mClosedHander.onCompleted(e);
mClosedHander = null;
}
}
@Override
public void close() {
closeInternal();
reportClose(null);
}
private void closeInternal() {
mKey.cancel();
try {
mChannel.close();
}
catch (IOException e) {
}
}
WritableCallback mWriteableHandler;
@Override
public void setWriteableCallback(WritableCallback handler) {
mWriteableHandler = handler;
}
DataCallback mDataHandler;
@Override
public void setDataCallback(DataCallback callback) {
mDataHandler = callback;
}
@Override
public DataCallback getDataCallback() {
return mDataHandler;
}
CompletedCallback mClosedHander;
@Override
public void setClosedCallback(CompletedCallback handler) {
mClosedHander = handler;
}
@Override
public CompletedCallback getClosedCallback() {
return mClosedHander;
}
@Override
public WritableCallback getWriteableCallback() {
return mWriteableHandler;
}
void reportEnd(Exception e) {
if (mEndReported)
return;
mEndReported = true;
if (mCompletedCallback != null)
mCompletedCallback.onCompleted(e);
else if (e != null) {
Log.e("NIO", "Unhandled exception", e);
}
}
boolean mEndReported;
Exception mPendingEndException;
void reportEndPending(Exception e) {
if (pending.hasRemaining()) {
mPendingEndException = e;
return;
}
reportEnd(e);
}
private CompletedCallback mCompletedCallback;
@Override
public void setEndCallback(CompletedCallback callback) {
mCompletedCallback = callback;
}
@Override
public CompletedCallback getEndCallback() {
return mCompletedCallback;
}
@Override
public boolean isOpen() {
return mChannel.isConnected() && mKey.isValid();
}
boolean mPaused = false;
@Override
public void pause() {
if (mServer.getAffinity() != Thread.currentThread()) {
mServer.run(new Runnable() {
@Override
public void run() {
pause();
}
});
return;
}
if (mPaused)
return;
mPaused = true;
try {
mKey.interestOps(~SelectionKey.OP_READ & mKey.interestOps());
}
catch (Exception ex) {
}
}
private void spitPending() {
if (pending.hasRemaining()) {
Util.emitAllData(this, pending);
}
}
@Override
public void resume() {
if (mServer.getAffinity() != Thread.currentThread()) {
mServer.run(new Runnable() {
@Override
public void run() {
resume();
}
});
return;
}
if (!mPaused)
return;
mPaused = false;
try {
mKey.interestOps(SelectionKey.OP_READ | mKey.interestOps());
}
catch (Exception ex) {
}
spitPending();
if (!isOpen())
reportEndPending(mPendingEndException);
}
@Override
public boolean isPaused() {
return mPaused;
}
@Override
public AsyncServer getServer() {
return mServer;
}
public InetSocketAddress getRemoteAddress() {
return socketAddress;
}
public InetAddress getLocalAddress() {
return mChannel.getLocalAddress();
}
public int getLocalPort() {
return mChannel.getLocalPort();
}
public Object getSocket() {
return getChannel().getSocket();
}
@Override
public String charset() {
return null;
}
}
================================================
FILE: androidasync/src/main/java/com/jeffmony/async/AsyncSSLException.java
================================================
package com.jeffmony.async;
public class AsyncSSLException extends Exception {
public AsyncSSLException(Throwable cause) {
super("Peer not trusted by any of the system trust managers.", cause);
}
private boolean mIgnore = false;
public void setIgnore(boolean ignore) {
mIgnore = ignore;
}
public boolean getIgnore() {
return mIgnore;
}
}
================================================
FILE: androidasync/src/main/java/com/jeffmony/async/AsyncSSLServerSocket.java
================================================
package com.jeffmony.async;
import java.security.PrivateKey;
import java.security.cert.Certificate;
public interface AsyncSSLServerSocket extends AsyncServerSocket {
PrivateKey getPrivateKey();
Certificate getCertificate();
}
================================================
FILE: androidasync/src/main/java/com/jeffmony/async/AsyncSSLSocket.java
================================================
package com.jeffmony.async;
import java.security.cert.X509Certificate;
import javax.net.ssl.SSLEngine;
public interface AsyncSSLSocket extends AsyncSocket {
X509Certificate[] getPeerCertificates();
SSLEngine getSSLEngine();
}
================================================
FILE: androidasync/src/main/java/com/jeffmony/async/AsyncSSLSocketWrapper.java
================================================
package com.jeffmony.async;
import android.content.Context;
import android.os.Build;
import android.util.Base64;
import android.util.Pair;
import com.jeffmony.async.callback.CompletedCallback;
import com.jeffmony.async.callback.ConnectCallback;
import com.jeffmony.async.callback.DataCallback;
import com.jeffmony.async.callback.ListenCallback;
import com.jeffmony.async.callback.WritableCallback;
import com.jeffmony.async.future.Cancellable;
import com.jeffmony.async.future.SimpleCancellable;
import com.jeffmony.async.http.SSLEngineSNIConfigurator;
import com.jeffmony.async.util.Allocator;
import com.jeffmony.async.util.StreamUtility;
import com.jeffmony.async.wrapper.AsyncSocketWrapper;
import org.apache.http.conn.ssl.StrictHostnameVerifier;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.math.BigInteger;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Calendar;
import java.util.Date;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLEngineResult.HandshakeStatus;
import javax.net.ssl.SSLEngineResult.Status;
import javax.net.ssl.SSLException;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
public class AsyncSSLSocketWrapper implements AsyncSocketWrapper, AsyncSSLSocket {
private static final String LOGTAG = "AsyncSSLSocketWrapper";
public interface HandshakeCallback {
public void onHandshakeCompleted(Exception e, AsyncSSLSocket socket);
}
static SSLContext defaultSSLContext;
static SSLContext trustAllSSLContext;
static TrustManager[] trustAllManagers;
static HostnameVerifier trustAllVerifier;
AsyncSocket mSocket;
BufferedDataSink mSink;
boolean mUnwrapping;
SSLEngine engine;
boolean finishedHandshake;
private int mPort;
private String mHost;
private boolean mWrapping;
HostnameVerifier hostnameVerifier;
HandshakeCallback handshakeCallback;
X509Certificate[] peerCertificates;
WritableCallback mWriteableCallback;
DataCallback mDataCallback;
TrustManager[] trustManagers;
boolean clientMode;
static {
// following is the "trust the system" certs setup
try {
// critical extension 2.5.29.15 is implemented improperly prior to 4.0.3.
// https://code.google.com/p/android/issues/detail?id=9307
// https://groups.google.com/forum/?fromgroups=#!topic/netty/UCfqPPk5O4s
// certs that use this extension will throw in Cipher.java.
// fallback is to use a custom SSLContext, and hack around the x509 extension.
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
throw new Exception();
defaultSSLContext = SSLContext.getInstance("Default");
}
catch (Exception ex) {
try {
defaultSSLContext = SSLContext.getInstance("TLS");
TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {
for (X509Certificate cert : certs) {
if (cert != null && cert.getCriticalExtensionOIDs() != null)
cert.getCriticalExtensionOIDs().remove("2.5.29.15");
}
}
} };
defaultSSLContext.init(null, trustAllCerts, null);
}
catch (Exception ex2) {
ex.printStackTrace();
ex2.printStackTrace();
}
}
try {
trustAllSSLContext = SSLContext.getInstance("TLS");
trustAllManagers = new TrustManager[] { new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {
}
} };
trustAllSSLContext.init(null, trustAllManagers, null);
trustAllVerifier = (hostname, session) -> true;
}
catch (Exception ex2) {
ex2.printStackTrace();
}
}
public static SSLContext getDefaultSSLContext() {
return defaultSSLContext;
}
public static void handshake(AsyncSocket socket,
String host, int port,
SSLEngine sslEngine,
TrustManager[] trustManagers, HostnameVerifier verifier, boolean clientMode,
final HandshakeCallback callback) {
AsyncSSLSocketWrapper wrapper = new AsyncSSLSocketWrapper(socket, host, port, sslEngine, trustManagers, verifier, clientMode);
wrapper.handshakeCallback = callback;
socket.setClosedCallback(new CompletedCallback() {
@Override
public void onCompleted(Exception ex) {
if (ex != null)
callback.onHandshakeCompleted(ex, null);
else
callback.onHandshakeCompleted(new SSLException("socket closed during handshake"), null);
}
});
try {
wrapper.engine.beginHandshake();
wrapper.handleHandshakeStatus(wrapper.engine.getHandshakeStatus());
} catch (SSLException e) {
wrapper.report(e);
}
}
public static Cancellable connectSocket(AsyncServer server, String host, int port, ConnectCallback callback) {
return connectSocket(server, host, port, false, callback);
}
public static Cancellable connectSocket(AsyncServer server, String host, int port, boolean trustAllCerts, ConnectCallback callback) {
SimpleCancellable cancellable = new SimpleCancellable();
Cancellable connect = server.connectSocket(host, port, (ex, netSocket) -> {
if (ex != null) {
if (cancellable.setComplete())
callback.onConnectCompleted(ex, null);
return;
}
handshake(netSocket, host, port,
(trustAllCerts ? trustAllSSLContext : defaultSSLContext).createSSLEngine(host, port),
trustAllCerts ? trustAllManagers : null,
trustAllCerts ? trustAllVerifier : null,
true, (e, socket) -> {
if (!cancellable.setComplete()) {
if (socket != null)
socket.close();
return;
}
if (e != null)
callback.onConnectCompleted(e, null);
else
callback.onConnectCompleted(null, socket);
});
});
cancellable.setParent(connect);
return cancellable;
}
boolean mEnded;
Exception mEndException;
final ByteBufferList pending = new ByteBufferList();
private AsyncSSLSocketWrapper(AsyncSocket socket,
String host, int port,
SSLEngine sslEngine,
TrustManager[] trustManagers, HostnameVerifier verifier, boolean clientMode) {
mSocket = socket;
hostnameVerifier = verifier;
this.clientMode = clientMode;
this.trustManagers = trustManagers;
this.engine = sslEngine;
mHost = host;
mPort = port;
engine.setUseClientMode(clientMode);
mSink = new BufferedDataSink(socket);
mSink.setWriteableCallback(new WritableCallback() {
@Override
public void onWriteable() {
if (mWriteableCallback != null)
mWriteableCallback.onWriteable();
}
});
// on pause, the emitter is paused to prevent the buffered
// socket and itself from firing.
// on resume, emitter is resumed, ssl buffer is flushed as well
mSocket.setEndCallback(new CompletedCallback() {
@Override
public void onCompleted(Exception ex) {
if (mEnded)
return;
mEnded = true;
mEndException = ex;
if (!pending.hasRemaining() && mEndCallback != null)
mEndCallback.onCompleted(ex);
}
});
mSocket.setDataCallback(dataCallback);
}
final DataCallback dataCallback = new DataCallback() {
final Allocator allocator = new Allocator().setMinAlloc(8192);
final ByteBufferList buffered = new ByteBufferList();
@Override
public void onDataAvailable(DataEmitter emitter, ByteBufferList bb) {
if (mUnwrapping)
return;
try {
mUnwrapping = true;
bb.get(buffered);
if (buffered.hasRemaining()) {
ByteBuffer all = buffered.getAll();
buffered.add(all);
}
ByteBuffer b = ByteBufferList.EMPTY_BYTEBUFFER;
while (true) {
if (b.remaining() == 0 && buffered.size() > 0) {
b = buffered.remove();
}
int remaining = b.remaining();
int before = pending.remaining();
SSLEngineResult res;
{
// wrap to prevent access to the readBuf
ByteBuffer readBuf = allocator.allocate();
res = engine.unwrap(b, readBuf);
addToPending(pending, readBuf);
allocator.track(pending.remaining() - before);
}
if (res.getStatus() == Status.BUFFER_OVERFLOW) {
allocator.setMinAlloc(allocator.getMinAlloc() * 2);
remaining = -1;
}
else if (res.getStatus() == Status.BUFFER_UNDERFLOW) {
buffered.addFirst(b);
if (buffered.size() <= 1) {
break;
}
// pack it
remaining = -1;
b = buffered.getAll();
buffered.addFirst(b);
b = ByteBufferList.EMPTY_BYTEBUFFER;
}
handleHandshakeStatus(res.getHandshakeStatus());
if (b.remaining() == remaining && before == pending.remaining()) {
buffered.addFirst(b);
break;
}
}
AsyncSSLSocketWrapper.this.onDataAvailable();
}
catch (SSLException ex) {
// ex.printStackTrace();
report(ex);
}
finally {
mUnwrapping = false;
}
}
};
public void onDataAvailable() {
Util.emitAllData(this, pending);
if (mEnded && !pending.hasRemaining() && mEndCallback != null)
mEndCallback.onCompleted(mEndException);
}
@Override
public SSLEngine getSSLEngine() {
return engine;
}
void addToPending(ByteBufferList out, ByteBuffer mReadTmp) {
mReadTmp.flip();
if (mReadTmp.hasRemaining()) {
out.add(mReadTmp);
}
else {
ByteBufferList.reclaim(mReadTmp);
}
}
@Override
public void end() {
mSocket.end();
}
public String getHost() {
return mHost;
}
public int getPort() {
return mPort;
}
private void handleHandshakeStatus(HandshakeStatus status) {
if (status == HandshakeStatus.NEED_TASK) {
final Runnable task = engine.getDelegatedTask();
task.run();
}
if (status == HandshakeStatus.NEED_WRAP) {
write(writeList);
}
if (status == HandshakeStatus.NEED_UNWRAP) {
dataCallback.onDataAvailable(this, new ByteBufferList());
}
try {
if (!finishedHandshake && (engine.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING || engine.getHandshakeStatus() == HandshakeStatus.FINISHED)) {
if (clientMode) {
Exception peerUnverifiedCause = null;
boolean trusted = false;
try {
peerCertificates = (X509Certificate[]) engine.getSession().getPeerCertificates();
if (mHost != null) {
if (hostnameVerifier == null) {
StrictHostnameVerifier verifier = new StrictHostnameVerifier();
verifier.verify(mHost, StrictHostnameVerifier.getCNs(peerCertificates[0]), StrictHostnameVerifier.getDNSSubjectAlts(peerCertificates[0]));
}
else {
if (!hostnameVerifier.verify(mHost, engine.getSession())) {
throw new SSLException("hostname <" + mHost + "> has been denied");
}
}
}
trusted = true;
}
catch (SSLException ex) {
peerUnverifiedCause = ex;
}
finishedHandshake = true;
if (!trusted) {
AsyncSSLException e = new AsyncSSLException(peerUnverifiedCause);
report(e);
if (!e.getIgnore())
throw e;
}
}
else {
finishedHandshake = true;
}
handshakeCallback.onHandshakeCompleted(null, this);
handshakeCallback = null;
mSocket.setClosedCallback(null);
// handshake can complete during a wrap, so make sure that the call
// stack and wrap flag is cleared before invoking writable
getServer().post(new Runnable() {
@Override
public void run() {
if (mWriteableCallback != null)
mWriteableCallback.onWriteable();
}
});
onDataAvailable();
}
}
catch (Exception ex) {
report(ex);
}
}
int calculateAlloc(int remaining) {
// alloc 50% more than we need for writing
int alloc = remaining * 3 / 2;
if (alloc == 0)
alloc = 8192;
return alloc;
}
ByteBufferList writeList = new ByteBufferList();
@Override
public void write(ByteBufferList bb) {
if (mWrapping)
return;
if (mSink.remaining() > 0)
return;
mWrapping = true;
int remaining;
SSLEngineResult res = null;
ByteBuffer writeBuf = ByteBufferList.obtain(calculateAlloc(bb.remaining()));
do {
// if the handshake is finished, don't send
// 0 bytes of data, since that makes the ssl connection die.
// it wraps a 0 byte package, and craps out.
if (finishedHandshake && bb.remaining() == 0)
break;
remaining = bb.remaining();
try {
ByteBuffer[] arr = bb.getAllArray();
res = engine.wrap(arr, writeBuf);
bb.addAll(arr);
writeBuf.flip();
writeList.add(writeBuf);
assert !writeList.hasRemaining();
if (writeList.remaining() > 0)
mSink.write(writeList);
int previousCapacity = writeBuf.capacity();
writeBuf = null;
if (res.getStatus() == Status.BUFFER_OVERFLOW) {
writeBuf = ByteBufferList.obtain(previousCapacity * 2);
remaining = -1;
}
else {
writeBuf = ByteBufferList.obtain(calculateAlloc(bb.remaining()));
handleHandshakeStatus(res.getHandshakeStatus());
}
}
catch (SSLException e) {
report(e);
}
}
while ((remaining != bb.remaining() || (res != null && res.getHandshakeStatus() == HandshakeStatus.NEED_WRAP)) && mSink.remaining() == 0);
mWrapping = false;
ByteBufferList.reclaim(writeBuf);
}
@Override
public void setWriteableCallback(WritableCallback handler) {
mWriteableCallback = handler;
}
@Override
public WritableCallback getWriteableCallback() {
return mWriteableCallback;
}
private void report(Exception e) {
final HandshakeCallback hs = handshakeCallback;
if (hs != null) {
handshakeCallback = null;
mSocket.setDataCallback(new DataCallback.NullDataCallback());
mSocket.end();
// handshake sets this callback. unset it.
mSocket.setClosedCallback(null);
mSocket.close();
hs.onHandshakeCompleted(e, null);
return;
}
CompletedCallback cb = getEndCallback();
if (cb != null)
cb.onCompleted(e);
}
@Override
public void setDataCallback(DataCallback callback) {
mDataCallback = callback;
}
@Override
public DataCallback getDataCallback() {
return mDataCallback;
}
@Override
public boolean isChunked() {
return mSocket.isChunked();
}
@Override
public boolean isOpen() {
return mSocket.isOpen();
}
@Override
public void close() {
mSocket.close();
}
@Override
public void setClosedCallback(CompletedCallback handler) {
mSocket.setClosedCallback(handler);
}
@Override
public CompletedCallback getClosedCallback() {
return mSocket.getClosedCallback();
}
CompletedCallback mEndCallback;
@Override
public void setEndCallback(CompletedCallback callback) {
mEndCallback = callback;
}
@Override
public CompletedCallback getEndCallback() {
return mEndCallback;
}
@Override
public void pause() {
mSocket.pause();
}
@Override
public void resume() {
mSocket.resume();
onDataAvailable();
}
@Override
public boolean isPaused() {
return mSocket.isPaused();
}
@Override
public AsyncServer getServer() {
return mSocket.getServer();
}
@Override
public AsyncSocket getSocket() {
return mSocket;
}
@Override
public DataEmitter getDataEmitter() {
return mSocket;
}
@Override
public X509Certificate[] getPeerCertificates() {
return peerCertificates;
}
@Override
public String charset() {
return null;
}
private static Certificate selfSign(KeyPair keyPair, String subjectDN) throws Exception
{
Provider bcProvider = new BouncyCastleProvider();
Security.addProvider(bcProvider);
long now = System.currentTimeMillis();
Date startDate = new Date(now);
X500Name dnName = new X500Name("CN=" + subjectDN);
BigInteger certSerialNumber = new BigInteger(Long.toString(now)); // <-- Using the current timestamp as the certificate serial number
Calendar calendar = Calendar.getInstance();
calendar.setTime(startDate);
calendar.add(Calendar.YEAR, 1); // <-- 1 Yr validity
Date endDate = calendar.getTime();
String signatureAlgorithm = "SHA256WithRSA"; // <-- Use appropriate signature algorithm based on your keyPair algorithm.
ContentSigner contentSigner = new JcaContentSignerBuilder(signatureAlgorithm).build(keyPair.getPrivate());
JcaX509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(dnName, certSerialNumber, startDate, endDate, dnName, keyPair.getPublic());
// Extensions --------------------------
// Basic Constraints
BasicConstraints basicConstraints = new BasicConstraints(true); // <-- true for CA, false for EndEntity
certBuilder.addExtension(new ASN1ObjectIdentifier("2.5.29.19"), true, basicConstraints); // Basic Constraints is usually marked as critical.
// -------------------------------------
return new JcaX509CertificateConverter().setProvider(bcProvider).getCertificate(certBuilder.build(contentSigner));
}
public static Pair selfSignCertificate(final Context context, String subjectName) throws Exception {
File keyPath = context.getFileStreamPath(subjectName + "-key.txt");
KeyPair pair;
Certificate cert;
try {
String[] keyParts = StreamUtility.readFile(keyPath).split("\n");
X509EncodedKeySpec pub = new X509EncodedKeySpec(Base64.decode(keyParts[0], 0));
PKCS8EncodedKeySpec priv = new PKCS8EncodedKeySpec(Base64.decode(keyParts[1], 0));
cert = CertificateFactory.getInstance("X.509").generateCertificate(new ByteArrayInputStream(Base64.decode(keyParts[2], 0)));
KeyFactory fact = KeyFactory.getInstance("RSA");
pair = new KeyPair(fact.generatePublic(pub), fact.generatePrivate(priv));
}
catch (Exception e) {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(2048);
pair = keyGen.generateKeyPair();
cert = selfSign(pair, subjectName);
StreamUtility.writeFile(keyPath,
Base64.encodeToString(pair.getPublic().getEncoded(), Base64.NO_WRAP)
+ "\n"
+ Base64.encodeToString(pair.getPrivate().getEncoded(), Base64.NO_WRAP)
+ "\n"
+ Base64.encodeToString(cert.getEncoded(), Base64.NO_WRAP));
}
return new Pair<>(pair, cert);
}
public static AsyncSSLServerSocket listenSecure(final Context context, final AsyncServer server, final String subjectName, final InetAddress host, final int port, final ListenCallback handler) {
final ObjectHolder holder = new ObjectHolder<>();
server.run(() -> {
try {
Pair keyCert = selfSignCertificate(context, subjectName);
KeyPair pair = keyCert.first;
Certificate cert = keyCert.second;
holder.held = listenSecure(server, pair.getPrivate(), cert, host, port, handler);
}
catch (Exception e) {
handler.onCompleted(e);
}
});
return holder.held;
}
public static AsyncSSLServerSocket listenSecure(AsyncServer server, String keyDer, String certDer, final InetAddress host, final int port, final ListenCallback handler) {
return listenSecure(server, Base64.decode(keyDer, Base64.DEFAULT), Base64.decode(certDer, Base64.DEFAULT), host, port, handler);
}
private static class ObjectHolder {
T held;
}
public static AsyncSSLServerSocket listenSecure(final AsyncServer server, final byte[] keyDer, final byte[] certDer, final InetAddress host, final int port, final ListenCallback handler) {
final ObjectHolder holder = new ObjectHolder<>();
server.run(() -> {
try {
PKCS8EncodedKeySpec key = new PKCS8EncodedKeySpec(keyDer);
Certificate cert = CertificateFactory.getInstance("X.509").generateCertificate(new ByteArrayInputStream(certDer));
PrivateKey pk = KeyFactory.getInstance("RSA").generatePrivate(key);
holder.held = listenSecure(server, pk, cert, host, port, handler);
}
catch (Exception e) {
handler.onCompleted(e);
}
});
return holder.held;
}
public static AsyncSSLServerSocket listenSecure(final AsyncServer server, final PrivateKey pk, final Certificate cert, final InetAddress host, final int port, final ListenCallback handler) {
final ObjectHolder holder = new ObjectHolder<>();
server.run(() -> {
try {
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(null);
ks.setKeyEntry("key", pk, null, new Certificate[] { cert });
KeyManagerFactory kmf = KeyManagerFactory.getInstance("X509");
kmf.init(ks, "".toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ks);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
final AsyncServerSocket socket = listenSecure(server, sslContext, host, port, handler);
holder.held = new AsyncSSLServerSocket() {
@Override
public PrivateKey getPrivateKey() {
return pk;
}
@Override
public Certificate getCertificate() {
return cert;
}
@Override
public void stop() {
socket.stop();
}
@Override
public int getLocalPort() {
return socket.getLocalPort();
}
};
}
catch (Exception e) {
handler.onCompleted(e);
}
});
return holder.held;
}
public static AsyncServerSocket listenSecure(AsyncServer server, final SSLContext sslContext, final InetAddress host, final int port, final ListenCallback handler) {
final SSLEngineSNIConfigurator conf = new SSLEngineSNIConfigurator() {
@Override
public SSLEngine createEngine(SSLContext sslContext, String peerHost, int peerPort) {
SSLEngine engine = super.createEngine(sslContext, peerHost, peerPort);
// String[] ciphers = engine.getEnabledCipherSuites();
// for (String cipher: ciphers) {
// Log.i(LOGTAG, cipher);
// }
// todo: what's this for? some vestigal vysor code i think. required by audio mirroring?
engine.setEnabledCipherSuites(new String[] { "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384" });
return engine;
}
};
return server.listen(host, port, new ListenCallback() {
@Override
public void onAccepted(final AsyncSocket socket) {
AsyncSSLSocketWrapper.handshake(socket, null, port, conf.createEngine(sslContext, null, port), null, null, false,
(e, sslSocket) -> {
if (e != null) {
// chrome seems to do some sort of SSL probe and cancels handshakes. not sure why.
// i suspect it is to pick an optimal strong cipher.
// seeing a lot of the following in the log (but no actual connection errors)
// javax.net.ssl.SSLHandshakeException: error:10000416:SSL routines:OPENSSL_internal:SSLV3_ALERT_CERTIFICATE_UNKNOWN
// seen on Shield TV running API 26
// todo fix: conscrypt ssl context?
// Log.e(LOGTAG, "Error while handshaking", e);
socket.close();
return;
}
handler.onAccepted(sslSocket);
});
}
@Override
public void onListening(AsyncServerSocket socket) {
handler.onListening(socket);
}
@Override
public void onCompleted(Exception ex) {
handler.onCompleted(ex);
}
});
}
}
================================================
FILE: androidasync/src/main/java/com/jeffmony/async/AsyncSemaphore.java
================================================
package com.jeffmony.async;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class AsyncSemaphore {
Semaphore semaphore = new Semaphore(0);
public void acquire() throws InterruptedException {
ThreadQueue threadQueue = ThreadQueue.getOrCreateThreadQueue(Thread.currentThread());
AsyncSemaphore last = threadQueue.waiter;
threadQueue.waiter = this;
Semaphore queueSemaphore = threadQueue.queueSemaphore;
try {
if (semaphore.tryAcquire())
return;
while (true) {
// run the queue
while (true) {
Runnable run = threadQueue.remove();
if (run == null)
break;
// Log.i(LOGTAG, "Pumping for AsyncSemaphore");
run.run();
}
int permits = Math.max(1, queueSemaphore.availablePermits());
queueSemaphore.acquire(permits);
if (semaphore.tryAcquire())
break;
}
}
finally {
threadQueue.waiter = last;
}
}
public boolean tryAcquire(long timeout, TimeUnit timeunit) throws InterruptedException {
long timeoutMs = TimeUnit.MILLISECONDS.convert(timeout, timeunit);
ThreadQueue threadQueue = ThreadQueue.getOrCreateThreadQueue(Thread.currentThread());
AsyncSemaphore last = threadQueue.waiter;
threadQueue.waiter = this;
Semaphore queueSemaphore = threadQueue.queueSemaphore;
try {
if (semaphore.tryAcquire())
return true;
long start = System.currentTimeMillis();
do {
// run the queue
while (true) {
Runnable run = threadQueue.remove();
if (run == null)
break;
// Log.i(LOGTAG, "Pumping for AsyncSemaphore");
run.run();
}
int permits = Math.max(1, queueSemaphore.availablePermits());
if (!queueSemaphore.tryAcquire(permits, timeoutMs, TimeUnit.MILLISECONDS))
return false;
if (semaphore.tryAcquire())
return true;
}
while (System.currentTimeMillis() - start < timeoutMs);
return false;
}
finally {
threadQueue.waiter = last;
}
}
public void release() {
semaphore.release();
ThreadQueue.release(this);
}
}
================================================
FILE: androidasync/src/main/java/com/jeffmony/async/AsyncServer.java
================================================
package com.jeffmony.async;
import android.os.Build;
import android.os.Handler;
import android.os.SystemClock;
import android.util.Log;
import com.jeffmony.async.callback.CompletedCallback;
import com.jeffmony.async.callback.ConnectCallback;
import com.jeffmony.async.callback.ListenCallback;
import com.jeffmony.async.callback.SocketCreateCallback;
import com.jeffmony.async.callback.ValueFunction;
import com.jeffmony.async.future.Cancellable;
import com.jeffmony.async.future.Future;
import com.jeffmony.async.future.FutureCallback;
import com.jeffmony.async.future.SimpleCancellable;
import com.jeffmony.async.future.SimpleFuture;
import com.jeffmony.async.util.StreamUtility;
import java.io.IOException;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.ClosedSelectorException;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.channels.spi.SelectorProvider;
import java.util.Arrays;
import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.Semaphore;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class AsyncServer {
public static final String LOGTAG = "NIO";
private static class RunnableWrapper implements Runnable {
boolean hasRun;
Runnable runnable;
ThreadQueue threadQueue;
Handler handler;
@Override
public void run() {
synchronized (this) {
if (hasRun)
return;
hasRun = true;
}
try {
runnable.run();
}
finally {
threadQueue.remove(this);
handler.removeCallbacks(this);
threadQueue = null;
handler = null;
runnable = null;
}
}
}
public static void post(Handler handler, Runnable runnable) {
RunnableWrapper wrapper = new RunnableWrapper();
ThreadQueue threadQueue = ThreadQueue.getOrCreateThreadQueue(handler.getLooper().getThread());
wrapper.threadQueue = threadQueue;
wrapper.handler = handler;
wrapper.runnable = runnable;
// run it in a blocking AsyncSemaphore or a Handler, whichever gets to it first.
threadQueue.add(wrapper);
handler.post(wrapper);
// run the queue if the thread is blocking
threadQueue.queueSemaphore.release();
}
static {
try {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.FROYO) {
java.lang.System.setProperty("java.net.preferIPv4Stack", "true");
java.lang.System.setProperty("java.net.preferIPv6Addresses", "false");
}
}
catch (Throwable ex) {
}
}
static AsyncServer mInstance = new AsyncServer();
public static AsyncServer getDefault() {
return mInstance;
}
private SelectorWrapper mSelector;
public boolean isRunning() {
return mSelector != null;
}
String mName;
public AsyncServer() {
this(null);
}
public AsyncServer(String name) {
if (name == null)
name = "AsyncServer";
mName = name;
}
private static ExecutorService synchronousWorkers = newSynchronousWorkers("AsyncServer-worker-");
private static void wakeup(final SelectorWrapper selector) {
synchronousWorkers.execute(() -> {
try {
selector.wakeupOnce();
}
catch (Exception e) {
}
});
}
boolean killed;
public void kill() {
synchronized (this) {
killed = true;
}
stop(false);
}
int postCounter = 0;
public Cancellable postDelayed(Runnable runnable, long delay) {
Scheduled s;
synchronized (this) {
if (killed)
return SimpleCancellable.CANCELLED;
// Calculate when to run this queue item:
// If there is a delay (non-zero), add it to the current time
// When delay is zero, ensure that this follows all other
// zero-delay queue items. This is done by setting the
// "time" to the queue size. This will make sure it is before
// all time-delayed queue items (for all real world scenarios)
// as it will always be less than the current time and also remain
// behind all other immediately run queue items.
long time;
if (delay > 0)
time = SystemClock.elapsedRealtime() + delay;
else if (delay == 0)
time = postCounter++;
else if (mQueue.size() > 0)
time = Math.min(0, mQueue.peek().time - 1);
else
time = 0;
mQueue.add(s = new Scheduled(this, runnable, time));
// start the server up if necessary
if (mSelector == null)
run();
if (!isAffinityThread()) {
wakeup(mSelector);
}
}
return s;
}
public Cancellable postImmediate(Runnable runnable) {
if (Thread.currentThread() == getAffinity()) {
runnable.run();
return null;
}
return postDelayed(runnable, -1);
}
public Cancellable post(Runnable runnable) {
return postDelayed(runnable, 0);
}
public Cancellable post(final CompletedCallback callback, final Exception e) {
return post(() -> callback.onCompleted(e));
}
public void run(final Runnable runnable) {
if (Thread.currentThread() == mAffinity) {
post(runnable);
lockAndRunQueue(this, mQueue);
return;
}
final Semaphore semaphore;
synchronized (this) {
if (killed)
return;
semaphore = new Semaphore(0);
post(() -> {
runnable.run();
semaphore.release();
});
}
try {
semaphore.acquire();
}
catch (InterruptedException e) {
Log.e(LOGTAG, "run", e);
}
}
private static class Scheduled implements Cancellable, Runnable {
// this constructor is only called when the async execution should not be preserved
// ie... AsyncServer.stop.
public Scheduled(AsyncServer server, Runnable runnable, long time) {
this.server = server;
this.runnable = runnable;
this.time = time;
}
public AsyncServer server;
public Runnable runnable;
public long time;
@Override
public void run() {
this.runnable.run();
}
@Override
public boolean isDone() {
synchronized (server) {
return !cancelled && !server.mQueue.contains(this);
}
}
boolean cancelled;
@Override
public boolean isCancelled() {
return cancelled;
}
@Override
public boolean cancel() {
synchronized (server) {
return cancelled = server.mQueue.remove(this);
}
}
}
PriorityQueue mQueue = new PriorityQueue(1, Scheduler.INSTANCE);
static class Scheduler implements Comparator {
public static Scheduler INSTANCE = new Scheduler();
private Scheduler() {
}
@Override
public int compare(Scheduled s1, Scheduled s2) {
// keep the smaller ones at the head, so they get tossed out quicker
if (s1.time == s2.time)
return 0;
if (s1.time > s2.time)
return 1;
return -1;
}
}
public void stop() {
stop(false);
}
public void stop(boolean wait) {
// Log.i(LOGTAG, "****AsyncServer is shutting down.****");
final SelectorWrapper currentSelector;
final Semaphore semaphore;
final boolean isAffinityThread;
synchronized (this) {
isAffinityThread = isAffinityThread();
currentSelector = mSelector;
if (currentSelector == null)
return;
semaphore = new Semaphore(0);
// post a shutdown and wait
mQueue.add(new Scheduled(this, new Runnable() {
@Override
public void run() {
shutdownEverything(currentSelector);
semaphore.release();
}
}, 0));
synchronousWorkers.execute(() -> {
try {
currentSelector.wakeupOnce();
}
catch (Exception e) {
}
});
// force any existing connections to die
shutdownKeys(currentSelector);
mQueue = new PriorityQueue<>(1, Scheduler.INSTANCE);
mSelector = null;
mAffinity = null;
}
try {
if (!isAffinityThread && wait)
semaphore.acquire();
}
catch (Exception e) {
}
}
protected void onDataReceived(int transmitted) {
}
protected void onDataSent(int transmitted) {
}
private static class ObjectHolder {
T held;
}
public AsyncServerSocket listen(final InetAddress host, final int port, final ListenCallback handler) {
final ObjectHolder holder = new ObjectHolder<>();
run(new Runnable() {
@Override
public void run() {
ServerSocketChannel closeableServer = null;
ServerSocketChannelWrapper closeableWrapper = null;
try {
closeableServer = ServerSocketChannel.open();
closeableWrapper = new ServerSocketChannelWrapper(
closeableServer);
final ServerSocketChannel server = closeableServer;
final ServerSocketChannelWrapper wrapper = closeableWrapper;
InetSocketAddress isa;
if (host == null)
isa = new InetSocketAddress(port);
else
isa = new InetSocketAddress(host, port);
server.socket().bind(isa);
final SelectionKey key = wrapper.register(mSelector.getSelector());
key.attach(handler);
handler.onListening(holder.held = new AsyncServerSocket() {
@Override
public int getLocalPort() {
return server.socket().getLocalPort();
}
@Override
public void stop() {
StreamUtility.closeQuietly(wrapper);
try {
key.cancel();
}
catch (Exception e) {
}
}
});
}
catch (IOException e) {
Log.e(LOGTAG, "wtf", e);
StreamUtility.closeQuietly(closeableWrapper, closeableServer);
handler.onCompleted(e);
}
}
});
return holder.held;
}
private class ConnectFuture extends SimpleFuture {
@Override
protected void cancelCleanup() {
super.cancelCleanup();
try {
if (socket != null)
socket.close();
}
catch (IOException e) {
}
}
SocketChannel socket;
ConnectCallback callback;
}
public Cancellable connectResolvedInetSocketAddress(final InetSocketAddress address, final ConnectCallback callback) {
return connectResolvedInetSocketAddress(address, callback, null);
}
public ConnectFuture connectResolvedInetSocketAddress(final InetSocketAddress address, final ConnectCallback callback, final SocketCreateCallback createCallback) {
final ConnectFuture cancel = new ConnectFuture();
assert !address.isUnresolved();
post(new Runnable() {
@Override
public void run() {
if (cancel.isCancelled())
return;
cancel.callback = callback;
SelectionKey ckey = null;
SocketChannel socket = null;
try {
socket = cancel.socket = SocketChannel.open();
socket.configureBlocking(false);
ckey = socket.register(mSelector.getSelector(), SelectionKey.OP_CONNECT);
ckey.attach(cancel);
if (createCallback != null)
createCallback.onSocketCreated(socket.socket().getLocalPort());
socket.connect(address);
}
catch (Throwable e) {
if (ckey != null)
ckey.cancel();
StreamUtility.closeQuietly(socket);
cancel.setComplete(new RuntimeException(e));
}
}
});
return cancel;
}
public Cancellable connectSocket(final InetSocketAddress remote, final ConnectCallback callback) {
if (!remote.isUnresolved())
return connectResolvedInetSocketAddress(remote, callback);
final SimpleFuture ret = new SimpleFuture();
Future lookup = getByName(remote.getHostName());
ret.setParent(lookup);
lookup
.setCallback(new FutureCallback() {
@Override
public void onCompleted(Exception e, InetAddress result) {
if (e != null) {
callback.onConnectCompleted(e, null);
ret.setComplete(e);
return;
}
ret.setComplete((ConnectFuture)connectResolvedInetSocketAddress(new InetSocketAddress(result, remote.getPort()), callback));
}
});
return ret;
}
public Cancellable connectSocket(final String host, final int port, final ConnectCallback callback) {
return connectSocket(InetSocketAddress.createUnresolved(host, port), callback);
}
private static ExecutorService newSynchronousWorkers(String prefix) {
ThreadFactory tf = new NamedThreadFactory(prefix);
ThreadPoolExecutor tpe = new ThreadPoolExecutor(1, 4, 10L,
TimeUnit.SECONDS, new LinkedBlockingQueue(), tf);
return tpe;
}
private static final Comparator ipSorter = new Comparator() {
@Override
public int compare(InetAddress lhs, InetAddress rhs) {
if (lhs instanceof Inet4Address && rhs instanceof Inet4Address)
return 0;
if (lhs instanceof Inet6Address && rhs instanceof Inet6Address)
return 0;
if (lhs instanceof Inet4Address && rhs instanceof Inet6Address)
return -1;
return 1;
}
};
private static ExecutorService synchronousResolverWorkers = newSynchronousWorkers("AsyncServer-resolver-");
public Future getAllByName(final String host) {
final SimpleFuture ret = new SimpleFuture();
synchronousResolverWorkers.execute(new Runnable() {
@Override
public void run() {
try {
final InetAddress[] result = InetAddress.getAllByName(host);
Arrays.sort(result, ipSorter);
if (result == null || result.length == 0)
throw new HostnameResolutionException("no addresses for host");
post(new Runnable() {
@Override
public void run() {
ret.setComplete(null, result);
}
});
} catch (final Exception e) {
post(new Runnable() {
@Override
public void run() {
ret.setComplete(e, null);
}
});
}
}
});
return ret;
}
public Future getByName(String host) {
return getAllByName(host).thenConvert(addresses -> addresses[0]);
}
private void handleSocket(final AsyncNetworkSocket handler) throws ClosedChannelException {
final ChannelWrapper sc = handler.getChannel();
SelectionKey ckey = sc.register(mSelector.getSelector());
ckey.attach(handler);
handler.setup(this, ckey);
}
public AsyncDatagramSocket connectDatagram(final String host, final int port) throws IOException {
final DatagramChannel socket = DatagramChannel.open();
final AsyncDatagramSocket handler = new AsyncDatagramSocket();
handler.attach(socket);
// ugh.. this should really be post to make it nonblocking...
// but i want datagrams to be immediately writable.
// they're not really used anyways.
run(new Runnable() {
@Override
public void run() {
try {
final SocketAddress remote = new InetSocketAddress(host, port);
handleSocket(handler);
socket.connect(remote);
}
catch (IOException e) {
Log.e(LOGTAG, "Datagram error", e);
StreamUtility.closeQuietly(socket);
}
}
});
return handler;
}
public AsyncDatagramSocket openDatagram() {
return openDatagram(null, 0, false);
}
public Cancellable createDatagram(String address, int port, boolean reuseAddress, FutureCallback callback) {
return createDatagram(() -> InetAddress.getByName(address), port, reuseAddress, callback);
}
public Cancellable createDatagram(InetAddress address, int port, boolean reuseAddress, FutureCallback callback) {
return createDatagram(() -> address, port, reuseAddress, callback);
}
private Cancellable createDatagram(ValueFunction inetAddressValueFunction, final int port, final boolean reuseAddress, FutureCallback callback) {
SimpleFuture ret = new SimpleFuture<>();
ret.setCallback(callback);
post(() -> {
DatagramChannel socket = null;
try {
socket = DatagramChannel.open();
final AsyncDatagramSocket handler = new AsyncDatagramSocket();
handler.attach(socket);
InetSocketAddress address;
if (inetAddressValueFunction == null)
address = new InetSocketAddress(port);
else
address = new InetSocketAddress(inetAddressValueFunction.getValue(), port);
if (reuseAddress)
socket.socket().setReuseAddress(reuseAddress);
socket.socket().bind(address);
handleSocket(handler);
if (!ret.setComplete(handler))
socket.close();
}
catch (Exception e) {
StreamUtility.closeQuietly(socket);
ret.setComplete(e);
}
});
return ret;
}
public AsyncDatagramSocket openDatagram(final InetAddress host, final int port, final boolean reuseAddress) {
final AsyncDatagramSocket handler = new AsyncDatagramSocket();
// ugh.. this should really be post to make it nonblocking...
// but i want datagrams to be immediately writable.
// they're not really used anyways.
Runnable runnable = () -> {
final DatagramChannel socket;
try {
socket = DatagramChannel.open();
}
catch (Exception e) {
return;
}
try {
handler.attach(socket);
InetSocketAddress address;
if (host == null)
address = new InetSocketAddress(port);
else
address = new InetSocketAddress(host, port);
if (reuseAddress)
socket.socket().setReuseAddress(reuseAddress);
socket.socket().bind(address);
handleSocket(handler);
}
catch (IOException e) {
Log.e(LOGTAG, "Datagram error", e);
StreamUtility.closeQuietly(socket);
}
};
if (getAffinity() != Thread.currentThread()) {
run(runnable);
return handler;
}
runnable.run();
return handler;
}
public AsyncDatagramSocket connectDatagram(final SocketAddress remote) throws IOException {
final AsyncDatagramSocket handler = new AsyncDatagramSocket();
final DatagramChannel socket = DatagramChannel.open();
handler.attach(socket);
// ugh.. this should really be post to make it nonblocking...
// but i want datagrams to be immediately writable.
// they're not really used anyways.
Runnable runnable = () -> {
try {
handleSocket(handler);
socket.connect(remote);
}
catch (IOException e) {
StreamUtility.closeQuietly(socket);
}
};
if (getAffinity() != Thread.currentThread()) {
run(runnable);
return handler;
}
runnable.run();
return handler;
}
final private static ThreadLocal threadServer = new ThreadLocal<>();
public static AsyncServer getCurrentThreadServer() {
return threadServer.get();
}
Thread mAffinity;
private void run() {
final SelectorWrapper selector;
final PriorityQueue queue;
synchronized (this) {
if (mSelector == null) {
try {
selector = mSelector = new SelectorWrapper(SelectorProvider.provider().openSelector());
queue = mQueue;
}
catch (IOException e) {
throw new RuntimeException("unable to create selector?", e);
}
mAffinity = new Thread(mName) {
public void run() {
try {
threadServer.set(AsyncServer.this);
AsyncServer.run(AsyncServer.this, selector, queue);
}
finally {
threadServer.remove();
}
}
};
mAffinity.start();
// kicked off the new thread, let's bail.
return;
}
// this is a reentrant call
selector = mSelector;
queue = mQueue;
// fall through to outside of the synchronization scope
// to allow the thread to run without locking.
}
try {
runLoop(this, selector, queue);
}
catch (AsyncSelectorException e) {
Log.i(LOGTAG, "Selector closed", e);
try {
// StreamUtility.closeQuiety is throwing ArrayStoreException?
selector.getSelector().close();
}
catch (Exception ex) {
}
}
}
private static void run(final AsyncServer server, final SelectorWrapper selector, final PriorityQueue queue) {
// Log.i(LOGTAG, "****AsyncServer is starting.****");
// at this point, this local queue and selector are owned
// by this thread.
// if a stop is called, the instance queue and selector
// will be replaced and nulled respectively.
// this will allow the old queue and selector to shut down
// gracefully, while also allowing a new selector thread
// to start up while the old one is still shutting down.
while(true) {
try {
runLoop(server, selector, queue);
}
catch (AsyncSelectorException e) {
if (!(e.getCause() instanceof ClosedSelectorException))
Log.i(LOGTAG, "Selector exception, shutting down", e);
StreamUtility.closeQuietly(selector);
}
// see if we keep looping, this must be in a synchronized block since the queue is accessed.
synchronized (server) {
if (selector.isOpen() && (selector.keys().size() > 0 || queue.size() > 0))
continue;
shutdownEverything(selector);
if (server.mSelector == selector) {
server.mQueue = new PriorityQueue(1, Scheduler.INSTANCE);
server.mSelector = null;
server.mAffinity = null;
}
break;
}
}
// Log.i(LOGTAG, "****AsyncServer has shut down.****");
}
private static void shutdownKeys(SelectorWrapper selector) {
try {
for (SelectionKey key: selector.keys()) {
StreamUtility.closeQuietly(key.channel());
try {
key.cancel();
}
catch (Exception e) {
}
}
}
catch (Exception ex) {
}
}
private static void shutdownEverything(SelectorWrapper selector) {
shutdownKeys(selector);
// SHUT. DOWN. EVERYTHING.
StreamUtility.closeQuietly(selector);
}
private static final long QUEUE_EMPTY = Long.MAX_VALUE;
private static long lockAndRunQueue(final AsyncServer server, final PriorityQueue queue) {
long wait = QUEUE_EMPTY;
// find the first item we can actually run
while (true) {
Scheduled run = null;
synchronized (server) {
long now = SystemClock.elapsedRealtime();
if (queue.size() > 0) {
Scheduled s = queue.remove();
if (s.time <= now) {
run = s;
}
else {
wait = s.time - now;
queue.add(s);
}
}
}
if (run == null)
break;
run.run();
}
server.postCounter = 0;
return wait;
}
private static class AsyncSelectorException extends IOException {
public AsyncSelectorException(Exception e) {
super(e);
}
}
private static void runLoop(final AsyncServer server, final SelectorWrapper selector, final PriorityQueue queue) throws AsyncSelectorException {
// Log.i(LOGTAG, "Keys: " + selector.keys().size());
boolean needsSelect = true;
// run the queue to populate the selector with keys
long wait = lockAndRunQueue(server, queue);
try {
synchronized (server) {
// select now to see if anything is ready immediately. this
// also clears the canceled key queue.
int readyNow = selector.selectNow();
if (readyNow == 0) {
// if there is nothing to select now, make sure we don't have an empty key set
// which means it would be time to turn this thread off.
if (selector.keys().size() == 0 && wait == QUEUE_EMPTY) {
// Log.i(LOGTAG, "Shutting down. keys: " + selector.keys().size() + " keepRunning: " + keepRunning);
return;
}
}
else {
needsSelect = false;
}
}
if (needsSelect) {
if (wait == QUEUE_EMPTY) {
// wait until woken up
selector.select();
}
else {
// nothing to select immediately but there's something pending so let's block that duration and wait.
selector.select(wait);
}
}
}
catch (Exception e) {
throw new AsyncSelectorException(e);
}
// process whatever keys are ready
Set readyKeys = selector.selectedKeys();
for (SelectionKey key: readyKeys) {
try {
if (key.isAcceptable()) {
ServerSocketChannel nextReady = (ServerSocketChannel) key.channel();
SocketChannel sc = null;
SelectionKey ckey = null;
try {
sc = nextReady.accept();
if (sc == null)
continue;
sc.configureBlocking(false);
ckey = sc.register(selector.getSelector(), SelectionKey.OP_READ);
ListenCallback serverHandler = (ListenCallback) key.attachment();
AsyncNetworkSocket handler = new AsyncNetworkSocket();
handler.attach(sc, (InetSocketAddress)sc.socket().getRemoteSocketAddress());
handler.setup(server, ckey);
ckey.attach(handler);
serverHandler.onAccepted(handler);
}
catch (IOException e) {
StreamUtility.closeQuietly(sc);
if (ckey != null)
ckey.cancel();
}
}
else if (key.isReadable()) {
AsyncNetworkSocket handler = (AsyncNetworkSocket) key.attachment();
int transmitted = handler.onReadable();
server.onDataReceived(transmitted);
}
else if (key.isWritable()) {
AsyncNetworkSocket handler = (AsyncNetworkSocket) key.attachment();
handler.onDataWritable();
}
else if (key.isConnectable()) {
ConnectFuture cancel = (ConnectFuture) key.attachment();
SocketChannel sc = (SocketChannel) key.channel();
key.interestOps(SelectionKey.OP_READ);
AsyncNetworkSocket newHandler;
try {
sc.finishConnect();
newHandler = new AsyncNetworkSocket();
newHandler.setup(server, key);
newHandler.attach(sc, (InetSocketAddress)sc.socket().getRemoteSocketAddress());
key.attach(newHandler);
}
catch (IOException ex) {
key.cancel();
StreamUtility.closeQuietly(sc);
if (cancel.setComplete(ex))
cancel.callback.onConnectCompleted(ex, null);
continue;
}
if (cancel.setComplete(newHandler))
cancel.callback.onConnectCompleted(null, newHandler);
}
else {
Log.i(LOGTAG, "wtf");
throw new RuntimeException("Unknown key state.");
}
}
catch (CancelledKeyException ex) {
}
}
readyKeys.clear();
}
public void dump() {
post(new Runnable() {
@Override
public void run() {
if (mSelector == null) {
Log.i(LOGTAG, "Server dump not possible. No selector?");
return;
}
Log.i(LOGTAG, "Key Count: " + mSelector.keys().size());
for (SelectionKey key: mSelector.keys()) {
Log.i(LOGTAG, "Key: " + key);
}
}
});
}
public Thread getAffinity() {
return mAffinity;
}
public boolean isAffinityThread() {
return mAffinity == Thread.currentThread();
}
public boolean isAffinityThreadOrStopped() {
Thread affinity = mAffinity;
return affinity == null || affinity == Thread.currentThread();
}
private static class NamedThreadFactory implements ThreadFactory {
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
NamedThreadFactory(String namePrefix) {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
this.namePrefix = namePrefix;
}
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement(), 0);
if (t.isDaemon()) t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY) {
t.setPriority(Thread.NORM_PRIORITY);
}
return t;
}
}
}
================================================
FILE: androidasync/src/main/java/com/jeffmony/async/AsyncServerSocket.java
================================================
package com.jeffmony.async;
public interface AsyncServerSocket {
void stop();
int getLocalPort();
}
================================================
FILE: androidasync/src/main/java/com/jeffmony/async/AsyncSocket.java
================================================
package com.jeffmony.async;
public interface AsyncSocket extends DataEmitter, DataSink {
AsyncServer getServer();
}
================================================
FILE: androidasync/src/main/java/com/jeffmony/async/BufferedDataSink.java
================================================
package com.jeffmony.async;
import com.jeffmony.async.callback.CompletedCallback;
import com.jeffmony.async.callback.WritableCallback;
public class BufferedDataSink implements DataSink {
DataSink mDataSink;
public BufferedDataSink(DataSink datasink) {
setDataSink(datasink);
}
public boolean isBuffering() {
return mPendingWrites.hasRemaining() || forceBuffering;
}
public boolean isWritable() {
synchronized (mPendingWrites) {
return mPendingWrites.remaining() < mMaxBuffer;
}
}
public DataSink getDataSink() {
return mDataSink;
}
boolean forceBuffering;
public void forceBuffering(boolean forceBuffering) {
this.forceBuffering = forceBuffering;
if (!forceBuffering)
writePending();
}
public void setDataSink(DataSink datasink) {
mDataSink = datasink;
mDataSink.setWriteableCallback(this::writePending);
}
private void writePending() {
if (forceBuffering)
return;
// Log.i("NIO", "Writing to buffer...");
boolean empty;
synchronized (mPendingWrites) {
mDataSink.write(mPendingWrites);
empty = mPendingWrites.isEmpty();
}
if (empty) {
if (endPending)
mDataSink.end();
}
if (empty && mWritable != null)
mWritable.onWriteable();
}
final ByteBufferList mPendingWrites = new ByteBufferList();
// before the data is queued, let inheritors know. allows for filters, without
// issues with having to filter before writing which may fail in the buffer.
protected void onDataAccepted(ByteBufferList bb) {
}
@Override
public void write(final ByteBufferList bb) {
if (getServer().getAffinity() != Thread.currentThread()) {
synchronized (mPendingWrites) {
if (mPendingWrites.remaining() >= mMaxBuffer)
return;
onDataAccepted(bb);
bb.get(mPendingWrites);
}
getServer().post(this::writePending);
return;
}
onDataAccepted(bb);
if (!isBuffering())
mDataSink.write(bb);
synchronized (mPendingWrites) {
bb.get(mPendingWrites);
}
}
WritableCallback mWritable;
@Override
public void setWriteableCallback(WritableCallback handler) {
mWritable = handler;
}
@Override
public WritableCallback getWriteableCallback() {
return mWritable;
}
public int remaining() {
return mPendingWrites.remaining();
}
int mMaxBuffer = Integer.MAX_VALUE;
public int getMaxBuffer() {
return mMaxBuffer;
}
public void setMaxBuffer(int maxBuffer) {
assert maxBuffer >= 0;
mMaxBuffer = maxBuffer;
}
@Override
public boolean isOpen() {
return mDataSink.isOpen();
}
boolean endPending;
@Override
public void end() {
if (getServer().getAffinity() != Thread.currentThread()) {
getServer().post(this::end);
return;
}
synchronized (mPendingWrites) {
if (mPendingWrites.hasRemaining()) {
endPending = true;
return;
}
}
mDataSink.end();
}
@Override
public void setClosedCallback(CompletedCallback handler) {
mDataSink.setClosedCallback(handler);
}
@Override
public CompletedCallback getClosedCallback() {
return mDataSink.getClosedCallback();
}
@Override
public AsyncServer getServer() {
return mDataSink.getServer();
}
}
================================================
FILE: androidasync/src/main/java/com/jeffmony/async/ByteBufferList.java
================================================
package com.jeffmony.async;
import android.annotation.TargetApi;
import android.os.Build;
import android.os.Looper;
import com.jeffmony.async.util.ArrayDeque;
import com.jeffmony.async.util.Charsets;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.Charset;
import java.util.Comparator;
import java.util.PriorityQueue;
@TargetApi(Build.VERSION_CODES.GINGERBREAD)
public class ByteBufferList {
ArrayDeque mBuffers = new ArrayDeque();
ByteOrder order = ByteOrder.BIG_ENDIAN;
public ByteOrder order() {
return order;
}
public ByteBufferList order(ByteOrder order) {
this.order = order;
return this;
}
public ByteBufferList() {
}
public ByteBufferList(ByteBuffer... b) {
addAll(b);
}
public ByteBufferList(byte[] buf) {
super();
ByteBuffer b = ByteBuffer.wrap(buf);
add(b);
}
public ByteBufferList addAll(ByteBuffer... bb) {
for (ByteBuffer b: bb)
add(b);
return this;
}
public ByteBufferList addAll(ByteBufferList... bb) {
for (ByteBufferList b: bb)
b.get(this);
return this;
}
public byte[] getBytes(int length) {
byte[] ret = new byte[length];
get(ret);
return ret;
}
public byte[] getAllByteArray() {
byte[] ret = new byte[remaining()];
get(ret);
return ret;
}
public ByteBuffer[] getAllArray() {
ByteBuffer[] ret = new ByteBuffer[mBuffers.size()];
ret = mBuffers.toArray(ret);
mBuffers.clear();
remaining = 0;
return ret;
}
public boolean isEmpty() {
return remaining == 0;
}
private int remaining = 0;
public int remaining() {
return remaining;
}
public boolean hasRemaining() {
return remaining() > 0;
}
public short peekShort() {
return read(2).getShort(mBuffers.peekFirst().position());
}
public byte peek() {
return read(1).get(mBuffers.peekFirst().position());
}
public int peekInt() {
return read(4).getInt(mBuffers.peekFirst().position());
}
public long peekLong() {
return read(8).getLong(mBuffers.peekFirst().position());
}
public byte[] peekBytes(int size) {
byte[] ret = new byte[size];
read(size).get(ret, mBuffers.peekFirst().position(), ret.length);
return ret;
}
public ByteBufferList skip(int length) {
get(null, 0, length);
return this;
}
public int getInt() {
int ret = read(4).getInt();
remaining -= 4;
return ret;
}
public char getByteChar() {
char ret = (char)read(1).get();
remaining--;
return ret;
}
public short getShort() {
short ret = read(2).getShort();
remaining -= 2;
return ret;
}
public byte get() {
byte ret = read(1).get();
remaining--;
return ret;
}
public long getLong() {
long ret = read(8).getLong();
remaining -= 8;
return ret;
}
public void get(byte[] bytes) {
get(bytes, 0, bytes.length);
}
public void get(byte[] bytes, int offset, int length) {
if (remaining() < length)
throw new IllegalArgumentException("length");
int need = length;
while (need > 0) {
ByteBuffer b = mBuffers.peek();
int read = Math.min(b.remaining(), need);
if (bytes != null){
b.get(bytes, offset, read);
} else {
//when bytes is null, just skip data.
b.position(b.position() + read);
}
need -= read;
offset += read;
if (b.remaining() == 0) {
ByteBuffer removed = mBuffers.remove();
assert b == removed;
reclaim(b);
}
}
remaining -= length;
}
public void get(ByteBufferList into, int length) {
if (remaining() < length)
throw new IllegalArgumentException("length");
int offset = 0;
while (offset < length) {
ByteBuffer b = mBuffers.remove();
int remaining = b.remaining();
if (remaining == 0) {
reclaim(b);
continue;
}
if (offset + remaining > length) {
int need = length - offset;
// this is shared between both
ByteBuffer subset = obtain(need);
subset.limit(need);
b.get(subset.array(), 0, need);
into.add(subset);
mBuffers.addFirst(b);
assert subset.capacity() >= need;
assert subset.position() == 0;
break;
}
else {
// this belongs to the new list
into.add(b);
}
offset += remaining;
}
remaining -= length;
}
public void get(ByteBufferList into) {
get(into, remaining());
}
public ByteBufferList get(int length) {
ByteBufferList ret = new ByteBufferList();
get(ret, length);
return ret.order(order);
}
public ByteBuffer getAll() {
if (remaining() == 0)
return EMPTY_BYTEBUFFER;
read(remaining());
return remove();
}
private ByteBuffer read(int count) {
if (remaining() < count)
throw new IllegalArgumentException("count : " + remaining() + "/" + count);
ByteBuffer first = mBuffers.peek();
while (first != null && !first.hasRemaining()) {
reclaim(mBuffers.remove());
first = mBuffers.peek();
}
if (first == null) {
return EMPTY_BYTEBUFFER;
}
if (first.remaining() >= count) {
return first.order(order);
}
ByteBuffer ret = obtain(count);
ret.limit(count);
byte[] bytes = ret.array();
int offset = 0;
ByteBuffer bb = null;
while (offset < count) {
bb = mBuffers.remove();
int toRead = Math.min(count - offset, bb.remaining());
bb.get(bytes, offset, toRead);
offset += toRead;
if (bb.remaining() == 0) {
reclaim(bb);
bb = null;
}
}
// if there was still data left in the last buffer we popped
// toss it back into the head
if (bb != null && bb.remaining() > 0)
mBuffers.addFirst(bb);
mBuffers.addFirst(ret);
return ret.order(order);
}
public void trim() {
// this clears out buffers that are empty in the beginning of the list
read(0);
}
public ByteBufferList add(ByteBufferList b) {
b.get(this);
return this;
}
public ByteBufferList add(ByteBuffer b) {
if (b.remaining() <= 0) {
// System.out.println("reclaiming remaining: " + b.remaining());
reclaim(b);
return this;
}
addRemaining(b.remaining());
// see if we can fit the entirety of the buffer into the end
// of the current last buffer
if (mBuffers.size() > 0) {
ByteBuffer last = mBuffers.getLast();
if (last.capacity() - last.limit() >= b.remaining()) {
last.mark();
last.position(last.limit());
last.limit(last.capacity());
last.put(b);
last.limit(last.position());
last.reset();
reclaim(b);
trim();
return this;
}
}
mBuffers.add(b);
trim();
return this;
}
public void addFirst(ByteBuffer b) {
if (b.remaining() <= 0) {
reclaim(b);
return;
}
addRemaining(b.remaining());
// see if we can fit the entirety of the buffer into the beginning
// of the current first buffer
if (mBuffers.size() > 0) {
ByteBuffer first = mBuffers.getFirst();
if (first.position() >= b.remaining()) {
first.position(first.position() - b.remaining());
first.mark();
first.put(b);
first.reset();
reclaim(b);
return;
}
}
mBuffers.addFirst(b);
}
private void addRemaining(int remaining) {
if (this.remaining() >= 0)
this.remaining += remaining;
}
public void recycle() {
while (mBuffers.size() > 0) {
reclaim(mBuffers.remove());
}
assert mBuffers.size() == 0;
remaining = 0;
}
public ByteBuffer remove() {
ByteBuffer ret = mBuffers.remove();
remaining -= ret.remaining();
return ret;
}
public int size() {
return mBuffers.size();
}
public void spewString() {
System.out.println(peekString());
}
public String peekString() {
return peekString(null);
}
// not doing toString as this is really nasty in the debugger...
public String peekString(Charset charset) {
if (charset == null)
charset = Charsets.UTF_8;
StringBuilder builder = new StringBuilder();
for (ByteBuffer bb: mBuffers) {
byte[] bytes;
int offset;
int length;
if (bb.isDirect()) {
bytes = new byte[bb.remaining()];
offset = 0;
length = bb.remaining();
bb.get(bytes);
}
else {
bytes = bb.array();
offset = bb.arrayOffset() + bb.position();
length = bb.remaining();
}
builder.append(new String(bytes, offset, length, charset));
}
return builder.toString();
}
public String readString() {
return readString(null);
}
public String readString(Charset charset) {
String ret = peekString(charset);
recycle();
return ret;
}
static class Reclaimer implements Comparator {
@Override
public int compare(ByteBuffer byteBuffer, ByteBuffer byteBuffer2) {
// keep the smaller ones at the head, so they get tossed out quicker
if (byteBuffer.capacity() == byteBuffer2.capacity())
return 0;
if (byteBuffer.capacity() > byteBuffer2.capacity())
return 1;
return -1;
}
}
static PriorityQueue reclaimed = new PriorityQueue(8, new Reclaimer());
private static PriorityQueue getReclaimed() {
Looper mainLooper = Looper.getMainLooper();
if (mainLooper != null) {
if (Thread.currentThread() == mainLooper.getThread())
return null;
}
return reclaimed;
}
private static int MAX_SIZE = 1024 * 1024;
public static int MAX_ITEM_SIZE = 1024 * 256;
static int currentSize = 0;
static int maxItem = 0;
public static void setMaxPoolSize(int size) {
MAX_SIZE = size;
}
public static void setMaxItemSize(int size) {
MAX_ITEM_SIZE = size;
}
private static boolean reclaimedContains(ByteBuffer b) {
for (ByteBuffer other: reclaimed) {
if (other == b)
return true;
}
return false;
}
public static void reclaim(ByteBuffer b) {
if (b == null || b.isDirect())
return;
if (b.arrayOffset() != 0 || b.array().length != b.capacity())
return;
if (b.capacity() < 8192)
return;
if (b.capacity() > MAX_ITEM_SIZE)
return;
PriorityQueue r = getReclaimed();
if (r == null)
return;
synchronized (LOCK) {
while (currentSize > MAX_SIZE && r.size() > 0 && r.peek().capacity() < b.capacity()) {
// System.out.println("removing for better: " + b.capacity());
ByteBuffer head = r.remove();
currentSize -= head.capacity();
}
if (currentSize > MAX_SIZE) {
// System.out.println("too full: " + b.capacity());
return;
}
assert !reclaimedContains(b);
b.position(0);
b.limit(b.capacity());
currentSize += b.capacity();
r.add(b);
assert r.size() != 0 ^ currentSize == 0;
maxItem = Math.max(maxItem, b.capacity());
}
}
private static final Object LOCK = new Object();
public static ByteBuffer obtain(int size) {
if (size <= maxItem) {
PriorityQueue r = getReclaimed();
if (r != null) {
synchronized (LOCK) {
while (r.size() > 0) {
ByteBuffer ret = r.remove();
if (r.size() == 0)
maxItem = 0;
currentSize -= ret.capacity();
assert r.size() != 0 ^ currentSize == 0;
if (ret.capacity() >= size) {
// System.out.println("using " + ret.capacity());
return ret;
}
// System.out.println("dumping " + ret.capacity());
}
}
}
}
// System.out.println("alloc for " + size);
ByteBuffer ret = ByteBuffer.allocate(Math.max(8192, size));
return ret;
}
public static void obtainArray(ByteBuffer[] arr, int size) {
PriorityQueue r = getReclaimed();
int index = 0;
int total = 0;
if (r != null) {
synchronized (LOCK) {
while (r.size() > 0 && total < size && index < arr.length - 1) {
ByteBuffer b = r.remove();
currentSize -= b.capacity();
assert r.size() != 0 ^ currentSize == 0;
int needed = Math.min(size - total, b.capacity());
total += needed;
arr[index++] = b;
}
}
}
if (total < size) {
ByteBuffer b = ByteBuffer.allocate(Math.max(8192, size - total));
arr[index++] = b;
}
for (int i = index; i < arr.length; i++) {
arr[i] = EMPTY_BYTEBUFFER;
}
}
public static ByteBuffer deepCopy(ByteBuffer copyOf) {
if (copyOf == null)
return null;
return (ByteBuffer)obtain(copyOf.remaining()).put(copyOf.duplicate()).flip();
}
public static final ByteBuffer EMPTY_BYTEBUFFER = ByteBuffer.allocate(0);
public static void writeOutputStream(OutputStream out, ByteBuffer b) throws IOException {
byte[] bytes;
int offset;
int length;
if (b.isDirect()) {
bytes = new byte[b.remaining()];
offset = 0;
length = b.remaining();
b.get(bytes);
}
else {
bytes = b.array();
offset = b.arrayOffset() + b.position();
length = b.remaining();
}
out.write(bytes, offset, length);
}
}
================================================
FILE: androidasync/src/main/java/com/jeffmony/async/ChannelWrapper.java
================================================
package com.jeffmony.async;
import java.io.IOException;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.ScatteringByteChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.spi.AbstractSelectableChannel;
abstract class ChannelWrapper implements ReadableByteChannel, ScatteringByteChannel {
private AbstractSelectableChannel mChannel;
ChannelWrapper(AbstractSelectableChannel channel) throws IOException {
channel.configureBlocking(false);
mChannel = channel;
}
public abstract void shutdownInput();
public abstract void shutdownOutput();
public abstract boolean isConnected();
public abstract int write(ByteBuffer src) throws IOException;
public abstract int write(ByteBuffer[] src) throws IOException;
// register for default events appropriate for this channel
public abstract SelectionKey register(Selector sel) throws ClosedChannelException;
public SelectionKey register(Selector sel, int ops) throws ClosedChannelException {
return mChannel.register(sel, ops);
}
public boolean isChunked() {
return false;
}
@Override
public boolean isOpen() {
return mChannel.isOpen();
}
@Override
public void close() throws IOException {
mChannel.close();
}
public abstract int getLocalPort();
public abstract InetAddress getLocalAddress();
public abstract Object getSocket();
}
================================================
FILE: androidasync/src/main/java/com/jeffmony/async/DataEmitter.java
================================================
package com.jeffmony.async;
import com.jeffmony.async.callback.CompletedCallback;
import com.jeffmony.async.callback.DataCallback;
public interface DataEmitter {
void setDataCallback(DataCallback callback);
DataCallback getDataCallback();
boolean isChunked();
void pause();
void resume();
void close();
boolean isPaused();
void setEndCallback(CompletedCallback callback);
CompletedCallback getEndCallback();
AsyncServer getServer();
String charset();
}
================================================
FILE: androidasync/src/main/java/com/jeffmony/async/DataEmitterBase.java
================================================
package com.jeffmony.async;
import com.jeffmony.async.callback.CompletedCallback;
import com.jeffmony.async.callback.DataCallback;
/**
* Created by koush on 5/27/13.
*/
public abstract class DataEmitterBase implements DataEmitter {
private boolean ended;
protected void report(Exception e) {
if (ended)
return;
ended = true;
if (getEndCallback() != null)
getEndCallback().onCompleted(e);
}
@Override
public final void setEndCallback(CompletedCallback callback) {
endCallback = callback;
}
CompletedCallback endCallback;
@Override
public final CompletedCallback getEndCallback() {
return endCallback;
}
DataCallback mDataCallback;
@Override
public void setDataCallback(DataCallback callback) {
mDataCallback = callback;
}
@Override
public DataCallback getDataCallback() {
return mDataCallback;
}
@Override
public String charset() {
return null;
}
}
================================================
FILE: androidasync/src/main/java/com/jeffmony/async/DataEmitterReader.java
================================================
package com.jeffmony.async;
import com.jeffmony.async.callback.DataCallback;
public class DataEmitterReader implements DataCallback {
DataCallback mPendingRead;
int mPendingReadLength;
ByteBufferList mPendingData = new ByteBufferList();
public void read(int count, DataCallback callback) {
assert mPendingRead == null;
mPendingReadLength = count;
mPendingRead = callback;
assert !mPendingData.hasRemaining();
mPendingData.recycle();
}
private boolean handlePendingData(DataEmitter emitter) {
if (mPendingReadLength > mPendingData.remaining())
return false;
DataCallback pendingRead = mPendingRead;
mPendingRead = null;
pendingRead.onDataAvailable(emitter, mPendingData);
assert !mPendingData.hasRemaining();
return true;
}
public DataEmitterReader() {
}
@Override
public void onDataAvailable(DataEmitter emitter, ByteBufferList bb) {
// if we're registered for data, we must be waiting for a read
assert mPendingRead != null;
do {
int need = Math.min(bb.remaining(), mPendingReadLength - mPendingData.remaining());
bb.get(mPendingData, need);
bb.remaining();
}
while (handlePendingData(emitter) && mPendingRead != null);
bb.remaining();
}
}
================================================
FILE: androidasync/src/main/java/com/jeffmony/async/DataSink.java
================================================
package com.jeffmony.async;
import com.jeffmony.async.callback.CompletedCallback;
import com.jeffmony.async.callback.WritableCallback;
public interface DataSink {
public void write(ByteBufferList bb);
public void setWriteableCallback(WritableCallback handler);
public WritableCallback getWriteableCallback();
public boolean isOpen();
public void end();
public void setClosedCallback(CompletedCallback handler);
public CompletedCallback getClosedCallback();
public AsyncServer getServer();
}
================================================
FILE: androidasync/src/main/java/com/jeffmony/async/DataTrackingEmitter.java
================================================
package com.jeffmony.async;
/**
* Created by koush on 5/28/13.
*/
public interface DataTrackingEmitter extends DataEmitter {
interface DataTracker {
void onData(int totalBytesRead);
}
void setDataTracker(DataTracker tracker);
DataTracker getDataTracker();
int getBytesRead();
void setDataEmitter(DataEmitter emitter);
}
================================================
FILE: androidasync/src/main/java/com/jeffmony/async/DatagramChannelWrapper.java
================================================
package com.jeffmony.async;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
class DatagramChannelWrapper extends ChannelWrapper {
DatagramChannel mChannel;
@Override
public InetAddress getLocalAddress() {
return mChannel.socket().getLocalAddress();
}
@Override
public int getLocalPort() {
return mChannel.socket().getLocalPort();
}
InetSocketAddress address;
public InetSocketAddress getRemoteAddress() {
return address;
}
public void disconnect() throws IOException {
mChannel.disconnect();
}
DatagramChannelWrapper(DatagramChannel channel) throws IOException {
super(channel);
mChannel = channel;
}
@Override
public int read(ByteBuffer buffer) throws IOException {
if (!isConnected()) {
int position = buffer.position();
address = (InetSocketAddress)mChannel.receive(buffer);
if (address == null)
return -1;
return buffer.position() - position;
}
address = null;
return mChannel.read(buffer);
}
@Override
public boolean isConnected() {
return mChannel.isConnected();
}
@Override
public int write(ByteBuffer src) throws IOException {
return mChannel.write(src);
}
@Override
public int write(ByteBuffer[] src) throws IOException {
return (int)mChannel.write(src);
}
@Override
public SelectionKey register(Selector sel, int ops) throws ClosedChannelException {
return mChannel.register(sel, ops);
}
@Override
public boolean isChunked() {
return true;
}
@Override
public SelectionKey register(Selector sel) throws ClosedChannelException {
return register(sel, SelectionKey.OP_READ);
}
@Override
public void shutdownOutput() {
}
@Override
public void shutdownInput() {
}
@Override
public long read(ByteBuffer[] byteBuffers) throws IOException {
return mChannel.read(byteBuffers);
}
@Override
public long read(ByteBuffer[] byteBuffers, int i, int i2) throws IOException {
return mChannel.read(byteBuffers, i, i2);
}
@Override
public Object getSocket() {
return mChannel.socket();
}
}
================================================
FILE: androidasync/src/main/java/com/jeffmony/async/FileDataEmitter.java
================================================
package com.jeffmony.async;
import com.jeffmony.async.callback.DataCallback;
import com.jeffmony.async.util.StreamUtility;
import java.io.File;
import java.io.FileInputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
/**
* Created by koush on 5/22/13.
*/
public class FileDataEmitter extends DataEmitterBase {
AsyncServer server;
File file;
public FileDataEmitter(AsyncServer server, File file) {
this.server = server;
this.file = file;
paused = !server.isAffinityThread();
if (!paused)
doResume();
}
DataCallback callback;
@Override
public void setDataCallback(DataCallback callback) {
this.callback = callback;
}
@Override
public DataCallback getDataCallback() {
return callback;
}
@Override
public boolean isChunked() {
return false;
}
boolean paused;
@Override
public void pause() {
paused = true;
}
@Override
public void resume() {
paused = false;
doResume();
}
@Override
protected void report(Exception e) {
StreamUtility.closeQuietly(channel);
super.report(e);
}
ByteBufferList pending = new ByteBufferList();
FileChannel channel;
Runnable pumper = new Runnable() {
@Override
public void run() {
try {
if (channel == null)
channel = new FileInputStream(file).getChannel();
if (!pending.isEmpty()) {
Util.emitAllData(FileDataEmitter.this, pending);
if (!pending.isEmpty())
return;
}
ByteBuffer b;
do {
b = ByteBufferList.obtain(8192);
if (-1 == channel.read(b)) {
report(null);
return;
}
b.flip();
pending.add(b);
Util.emitAllData(FileDataEmitter.this, pending);
}
while (pending.remaining() == 0 && !isPaused());
}
catch (Exception e) {
report(e);
}
}
};
private void doResume() {
server.post(pumper);
}
@Override
public boolean isPaused() {
return paused;
}
@Override
public AsyncServer getServer() {
return server;
}
@Override
public void close() {
try {
channel.close();
}
catch (Exception e) {
}
}
}
================================================
FILE: androidasync/src/main/java/com/jeffmony/async/FilteredDataEmitter.java
================================================
package com.jeffmony.async;
import com.jeffmony.async.callback.CompletedCallback;
import com.jeffmony.async.callback.DataCallback;
import com.jeffmony.async.wrapper.DataEmitterWrapper;
public class FilteredDataEmitter extends DataEmitterBase implements DataEmitter, DataCallback, DataEmitterWrapper, DataTrackingEmitter {
private DataEmitter mEmitter;
@Override
public DataEmitter getDataEmitter() {
return mEmitter;
}
@Override
public void setDataEmitter(DataEmitter emitter) {
if (mEmitter != null) {
mEmitter.setDataCallback(null);
}
mEmitter = emitter;
mEmitter.setDataCallback(this);
mEmitter.setEndCallback(new CompletedCallback() {
@Override
public void onCompleted(Exception ex) {
report(ex);
}
});
}
@Override
public int getBytesRead() {
return totalRead;
}
@Override
public DataTracker getDataTracker() {
return tracker;
}
@Override
public void setDataTracker(DataTracker tracker) {
this.tracker = tracker;
}
private DataTracker tracker;
private int totalRead;
@Override
public void onDataAvailable(DataEmitter emitter, ByteBufferList bb) {
if (closed) {
// this emitter was closed but for some reason data is still being spewed...
// eat it, nom nom.
bb.recycle();
return;
}
if (bb != null)
totalRead += bb.remaining();
Util.emitAllData(this, bb);
if (bb != null)
totalRead -= bb.remaining();
if (tracker != null && bb != null)
tracker.onData(totalRead);
// if there's data after the emitting, and it is paused... the underlying implementation
// is obligated to cache the byte buffer list.
}
@Override
public boolean isChunked() {
return mEmitter.isChunked();
}
@Override
public void pause() {
mEmitter.pause();
}
@Override
public void resume() {
mEmitter.resume();
}
@Override
public boolean isPaused() {
return mEmitter.isPaused();
}
@Override
public AsyncServer getServer() {
return mEmitter.getServer();
}
boolean closed;
@Override
public void close() {
closed = true;
if (mEmitter != null)
mEmitter.close();
}
@Override
public String charset() {
if (mEmitter == null)
return null;
return mEmitter.charset();
}
}
================================================
FILE: androidasync/src/main/java/com/jeffmony/async/FilteredDataSink.java
================================================
package com.jeffmony.async;
public class FilteredDataSink extends BufferedDataSink {
public FilteredDataSink(DataSink sink) {
super(sink);
setMaxBuffer(0);
}
public ByteBufferList filter(ByteBufferList bb) {
return bb;
}
@Override
protected void onDataAccepted(ByteBufferList bb) {
ByteBufferList filtered = filter(bb);
// filtering may return the same byte buffer, so watch for that.
if (filtered != bb) {
bb.recycle();
filtered.get(bb);
}
}
}
================================================
FILE: androidasync/src/main/java/com/jeffmony/async/HostnameResolutionException.java
================================================
package com.jeffmony.async;
public class HostnameResolutionException extends Exception {
public HostnameResolutionException(String message) {
super(message);
}
}
================================================
FILE: androidasync/src/main/java/com/jeffmony/async/LineEmitter.java
================================================
package com.jeffmony.async;
import com.jeffmony.async.callback.DataCallback;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
public class LineEmitter implements DataCallback {
public interface StringCallback {
void onStringAvailable(String s);
}
public LineEmitter() {
this(null);
}
public LineEmitter(Charset charset) {
this.charset = charset;
}
Charset charset;
ByteBufferList data = new ByteBufferList();
StringCallback mLineCallback;
public void setLineCallback(StringCallback callback) {
mLineCallback = callback;
}
public StringCallback getLineCallback() {
return mLineCallback;
}
@Override
public void onDataAvailable(DataEmitter emitter, ByteBufferList bb) {
ByteBuffer buffer = ByteBuffer.allocate(bb.remaining());
while (bb.remaining() > 0) {
byte b = bb.get();
if (b == '\n') {
assert mLineCallback != null;
buffer.flip();
data.add(buffer);
mLineCallback.onStringAvailable(data.readString(charset));
data = new ByteBufferList();
return;
}
else {
buffer.put(b);
}
}
buffer.flip();
data.add(buffer);
}
}
================================================
FILE: androidasync/src/main/java/com/jeffmony/async/PushParser.java
================================================
package com.jeffmony.async;
import android.util.Log;
import com.jeffmony.async.callback.DataCallback;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.LinkedList;
public class PushParser implements DataCallback {
public interface ParseCallback {
public void parsed(T data);
}
static abstract class Waiter {
int length;
public Waiter(int length) {
this.length = length;
}
/**
* Consumes received data, and/or returns next waiter to continue reading instead of this waiter.
* @param bb received data, bb.remaining >= length
* @return - a waiter that should continue reading right away, or null if this waiter is finished
*/
public abstract Waiter onDataAvailable(DataEmitter emitter, ByteBufferList bb);
}
static class IntWaiter extends Waiter {
ParseCallback callback;
public IntWaiter(ParseCallback callback) {
super(4);
this.callback = callback;
}
@Override
public Waiter onDataAvailable(DataEmitter emitter, ByteBufferList bb) {
callback.parsed(bb.getInt());
return null;
}
}
static class ByteArrayWaiter extends Waiter {
ParseCallback callback;
public ByteArrayWaiter(int length, ParseCallback callback) {
super(length);
if (length <= 0)
throw new IllegalArgumentException("length should be > 0");
this.callback = callback;
}
@Override
public Waiter onDataAvailable(DataEmitter emitter, ByteBufferList bb) {
byte[] bytes = new byte[length];
bb.get(bytes);
callback.parsed(bytes);
return null;
}
}
static class LenByteArrayWaiter extends Waiter {
private final ParseCallback callback;
public LenByteArrayWaiter(ParseCallback callback) {
super(4);
this.callback = callback;
}
@Override
public Waiter onDataAvailable(DataEmitter emitter, ByteBufferList bb) {
int length = bb.getInt();
if (length == 0) {
callback.parsed(new byte[0]);
return null;
}
return new ByteArrayWaiter(length, callback);
}
}
static class ByteBufferListWaiter extends Waiter {
ParseCallback callback;
public ByteBufferListWaiter(int length, ParseCallback callback) {
super(length);
if (length <= 0) throw new IllegalArgumentException("length should be > 0");
this.callback = callback;
}
@Override
public Waiter onDataAvailable(DataEmitter emitter, ByteBufferList bb) {
callback.parsed(bb.get(length));
return null;
}
}
static class LenByteBufferListWaiter extends Waiter {
private final ParseCallback callback;
public LenByteBufferListWaiter(ParseCallback callback) {
super(4);
this.callback = callback;
}
@Override
public Waiter onDataAvailable(DataEmitter emitter, ByteBufferList bb) {
int length = bb.getInt();
return new ByteBufferListWaiter(length, callback);
}
}
static class UntilWaiter extends Waiter {
byte value;
DataCallback callback;
public UntilWaiter(byte value, DataCallback callback) {
super(1);
this.value = value;
this.callback = callback;
}
@Override
public Waiter onDataAvailable(DataEmitter emitter, ByteBufferList bb) {
boolean found = true;
ByteBufferList cb = new ByteBufferList();
while (bb.size() > 0) {
ByteBuffer b = bb.remove();
b.mark();
int index = 0;
while (b.remaining() > 0 && !(found = (b.get() == value))) {
index++;
}
b.reset();
if (found) {
bb.addFirst(b);
bb.get(cb, index);
// eat the one we're waiting on
bb.get();
break;
} else {
cb.add(b);
}
}
callback.onDataAvailable(emitter, cb);
if (found) {
return null;
} else {
return this;
}
}
}
private class TapWaiter extends Waiter {
private final TapCallback callback;
public TapWaiter(TapCallback callback) {
super(0);
this.callback = callback;
}
@Override
public Waiter onDataAvailable(DataEmitter emitter, ByteBufferList bb) {
Method method = getTap(callback);
method.setAccessible(true);
try {
method.invoke(callback, args.toArray());
} catch (Exception e) {
Log.e("PushParser", "Error while invoking tap callback", e);
}
args.clear();
return null;
}
}
private Waiter noopArgWaiter = new Waiter(0) {
@Override
public Waiter onDataAvailable(DataEmitter emitter, ByteBufferList bb) {
args.add(null);
return null;
}
};
private Waiter byteArgWaiter = new Waiter(1) {
@Override
public Waiter onDataAvailable(DataEmitter emitter, ByteBufferList bb) {
args.add(bb.get());
return null;
}
};
private Waiter shortArgWaiter = new Waiter(2) {
@Override
public Waiter onDataAvailable(DataEmitter emitter, ByteBufferList bb) {
args.add(bb.getShort());
return null;
}
};
private Waiter intArgWaiter = new Waiter(4) {
@Override
public Waiter onDataAvailable(DataEmitter emitter, ByteBufferList bb) {
args.add(bb.getInt());
return null;
}
};
private Waiter longArgWaiter = new Waiter(8) {
@Override
public Waiter onDataAvailable(DataEmitter emitter, ByteBufferList bb) {
args.add(bb.getLong());
return null;
}
};
private ParseCallback byteArrayArgCallback = new ParseCallback() {
@Override
public void parsed(byte[] data) {
args.add(data);
}
};
private ParseCallback byteBufferListArgCallback = new ParseCallback() {
@Override
public void parsed(ByteBufferList data) {
args.add(data);
}
};
private ParseCallback stringArgCallback = new ParseCallback() {
@Override
public void parsed(byte[] data) {
args.add(new String(data));
}
};
DataEmitter mEmitter;
private LinkedList mWaiting = new LinkedList();
private ArrayList