diff --git a/exoplayerclient/.gitignore b/exoplayerclient/.gitignore new file mode 100644 index 0000000..656b534 --- /dev/null +++ b/exoplayerclient/.gitignore @@ -0,0 +1,16 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +zerotier.properties diff --git a/exoplayerclient/.idea/.gitignore b/exoplayerclient/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/exoplayerclient/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/exoplayerclient/.idea/.name b/exoplayerclient/.idea/.name new file mode 100644 index 0000000..a8da136 --- /dev/null +++ b/exoplayerclient/.idea/.name @@ -0,0 +1 @@ +ZeroTier Sockets Android ExoPlayer client \ No newline at end of file diff --git a/exoplayerclient/.idea/compiler.xml b/exoplayerclient/.idea/compiler.xml new file mode 100644 index 0000000..b589d56 --- /dev/null +++ b/exoplayerclient/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/exoplayerclient/.idea/gradle.xml b/exoplayerclient/.idea/gradle.xml new file mode 100644 index 0000000..a2d7c21 --- /dev/null +++ b/exoplayerclient/.idea/gradle.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/exoplayerclient/.idea/misc.xml b/exoplayerclient/.idea/misc.xml new file mode 100644 index 0000000..773fe0f --- /dev/null +++ b/exoplayerclient/.idea/misc.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/exoplayerclient/.idea/vcs.xml b/exoplayerclient/.idea/vcs.xml new file mode 100644 index 0000000..6c0b863 --- /dev/null +++ b/exoplayerclient/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/exoplayerclient/app/.gitignore b/exoplayerclient/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/exoplayerclient/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/exoplayerclient/app/build.gradle b/exoplayerclient/app/build.gradle new file mode 100644 index 0000000..57d0e48 --- /dev/null +++ b/exoplayerclient/app/build.gradle @@ -0,0 +1,48 @@ +plugins { + id 'com.android.application' +} + +// Creates a variable called zerotierPropertiesFile, and initializes it to the +// zerotier.properties file. +def zerotierPropertiesFile = rootProject.file("zerotier.properties") + +// Initializes a new Properties() object called zerotierProperties. +def zerotierProperties = new Properties() + +// Loads the zerotier.properties file into the zerotierProperties object. +zerotierProperties.load(new FileInputStream(zerotierPropertiesFile)) + +android { + namespace 'com.zerotier.sockets.exoplayerclient' + compileSdk 33 + + defaultConfig { + applicationId "com.zerotier.sockets.exoplayerclient" + minSdk 24 + targetSdk 33 + versionCode 1 + versionName "1.0" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'com.google.android.material:material:1.9.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' + implementation "androidx.media3:media3-exoplayer:1.1.0" + implementation "androidx.media3:media3-exoplayer-dash:1.1.0" + implementation "androidx.media3:media3-ui:1.1.0" + implementation 'androidx.media3:media3-datasource-okhttp:1.1.0' + implementation files(zerotierProperties['libzt.aar']) +} \ No newline at end of file diff --git a/exoplayerclient/app/proguard-rules.pro b/exoplayerclient/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/exoplayerclient/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# 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 \ No newline at end of file diff --git a/exoplayerclient/app/src/main/AndroidManifest.xml b/exoplayerclient/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..8e6aafb --- /dev/null +++ b/exoplayerclient/app/src/main/AndroidManifest.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/exoplayerclient/app/src/main/java/com/zerotier/sockets/ZeroTierSocketsSocketFactory.java b/exoplayerclient/app/src/main/java/com/zerotier/sockets/ZeroTierSocketsSocketFactory.java new file mode 100644 index 0000000..b8e22e6 --- /dev/null +++ b/exoplayerclient/app/src/main/java/com/zerotier/sockets/ZeroTierSocketsSocketFactory.java @@ -0,0 +1,327 @@ +package com.zerotier.sockets; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.Socket; +import java.net.SocketAddress; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.nio.channels.SocketChannel; + +import javax.net.SocketFactory; + +public class ZeroTierSocketsSocketFactory extends SocketFactory { + + String remoteAddr; + int port; + + public ZeroTierSocketsSocketFactory(String remoteAddr, int port) { + this.remoteAddr = remoteAddr; + this.port = port; + } + + @Override + public Socket createSocket() throws IOException, UnknownHostException { + return new SocketProxy(remoteAddr, port); + } + + @Override + public Socket createSocket(String host, int port) throws IOException, UnknownHostException { + throw new RuntimeException("Unimplemented"); + } + + @Override + public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException { + throw new RuntimeException("Unimplemented"); + } + + @Override + public Socket createSocket(InetAddress host, int port) throws IOException { + throw new RuntimeException("Unimplemented"); + } + + @Override + public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException { + throw new RuntimeException("Unimplemented"); + } + + static class SocketProxy extends Socket { + + String remoteAddr; + int port; + // + // creating new ZeroTierSocket() tries to connect immediately, + // so delay creating ZeroTierSocket() until connect() + // + ZeroTierSocket sock; + + public SocketProxy(String remoteAddr, int port) { + + // Socket logic + + this.remoteAddr = remoteAddr; + this.port = port; + } + + @Override + public void bind(SocketAddress bindpoint) { + throw new RuntimeException("Unimplemented"); + } + + @Override + public void close() throws IOException { + + // + // called by OkHttp + // + + if (sock != null) { + sock.close(); + } + } + + @Override + public void connect(SocketAddress endpoint) { + throw new RuntimeException("Unimplemented"); + } + + @Override + public void connect(SocketAddress endpoint, int timeout) throws IOException { + + // + // called by OkHttp + // + + // + // endpoint is something like: www.example.com/93.184.216.34:80 + // + + // + // timeout is something like 10000 + // + + sock = new ZeroTierSocket(remoteAddr, port, timeout); + } + + @Override + public SocketChannel getChannel() { + throw new RuntimeException("Unimplemented"); + } + + @Override + public InetAddress getInetAddress() { + throw new RuntimeException("Unimplemented"); + } + + @Override + public InputStream getInputStream() throws SocketException { + + // + // called by OkHttp + // + + return sock.getInputStream(); + } + + @Override + public boolean getKeepAlive() { + throw new RuntimeException("Unimplemented"); + } + + @Override + public InetAddress getLocalAddress() { + throw new RuntimeException("Unimplemented"); + } + + @Override + public int getLocalPort() { + throw new RuntimeException("Unimplemented"); + } + + @Override + public SocketAddress getLocalSocketAddress() { + throw new RuntimeException("Unimplemented"); + } + + @Override + public boolean getOOBInline() { + throw new RuntimeException("Unimplemented"); + } + + @Override + public OutputStream getOutputStream() throws SocketException { + + // + // called by OkHttp + // + + return sock.getOutputStream(); + } + + @Override + public int getPort() { + throw new RuntimeException("Unimplemented"); + } + + @Override + public int getReceiveBufferSize() { + throw new RuntimeException("Unimplemented"); + } + + @Override + public SocketAddress getRemoteSocketAddress() { + throw new RuntimeException("Unimplemented"); + } + + @Override + public boolean getReuseAddress() { + throw new RuntimeException("Unimplemented"); + } + + @Override + public int getSendBufferSize() { + throw new RuntimeException("Unimplemented"); + } + + @Override + public int getSoLinger() { + throw new RuntimeException("Unimplemented"); + } + + @Override + public int getSoTimeout() { + throw new RuntimeException("Unimplemented"); + } + + @Override + public boolean getTcpNoDelay() { + throw new RuntimeException("Unimplemented"); + } + + @Override + public int getTrafficClass() { + throw new RuntimeException("Unimplemented"); + } + + @Override + public boolean isBound() { + throw new RuntimeException("Unimplemented"); + } + + @Override + public boolean isClosed() { + + // + // called by OkHttp + // + + return sock == null || sock.isClosed(); + } + + @Override + public boolean isConnected() { + throw new RuntimeException("Unimplemented"); + } + + @Override + public boolean isInputShutdown() { + + // + // called by OkHttp + // + + return sock == null || sock.inputStreamHasBeenShutdown(); + } + + @Override + public boolean isOutputShutdown() { + + // + // called by OkHttp + // + + return sock == null || sock.outputStreamHasBeenShutdown(); + } + + @Override + public void sendUrgentData(int data) { + throw new RuntimeException("Unimplemented"); + } + + @Override + public void setKeepAlive(boolean on) { + throw new RuntimeException("Unimplemented"); + } + + @Override + public void setOOBInline(boolean on) { + throw new RuntimeException("Unimplemented"); + } + + @Override + public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) { + throw new RuntimeException("Unimplemented"); + } + + @Override + public void setReceiveBufferSize(int size) { + throw new RuntimeException("Unimplemented"); + } + + @Override + public void setReuseAddress(boolean on) { + throw new RuntimeException("Unimplemented"); + } + + @Override + public void setSendBufferSize(int size) { + throw new RuntimeException("Unimplemented"); + } + + @Override + public void setSoLinger(boolean on, int linger) { + throw new RuntimeException("Unimplemented"); + } + + @Override + public void setSoTimeout(int timeout) throws SocketException { + + // + // called by OkHttp + // + + if (sock == null) { + return; + } + + sock.setSoTimeout(timeout); + } + + @Override + public void setTcpNoDelay(boolean on) { + throw new RuntimeException("Unimplemented"); + } + + @Override + public void setTrafficClass(int tc) { + throw new RuntimeException("Unimplemented"); + } + + @Override + public void shutdownInput() { + throw new RuntimeException("Unimplemented"); + } + + @Override + public void shutdownOutput() { + throw new RuntimeException("Unimplemented"); + } + + @Override + public String toString() { + return "proxy(" + (sock == null ? "" : sock.toString()) + ")"; + } + } +} diff --git a/exoplayerclient/app/src/main/java/com/zerotier/sockets/exoplayerclient/MainActivity.java b/exoplayerclient/app/src/main/java/com/zerotier/sockets/exoplayerclient/MainActivity.java new file mode 100644 index 0000000..4a36858 --- /dev/null +++ b/exoplayerclient/app/src/main/java/com/zerotier/sockets/exoplayerclient/MainActivity.java @@ -0,0 +1,103 @@ +package com.zerotier.sockets.exoplayerclient; + +import android.content.Context; +import android.os.Bundle; +import android.util.Log; + +import androidx.appcompat.app.AppCompatActivity; +import androidx.media3.common.MediaItem; +import androidx.media3.datasource.DefaultDataSource; +import androidx.media3.datasource.okhttp.OkHttpDataSource; +import androidx.media3.exoplayer.ExoPlayer; +import androidx.media3.exoplayer.source.DefaultMediaSourceFactory; +import androidx.media3.ui.PlayerView; + +import com.zerotier.sockets.ZeroTierNative; +import com.zerotier.sockets.ZeroTierNode; +import com.zerotier.sockets.ZeroTierSocketsSocketFactory; + +import okhttp3.Call; +import okhttp3.OkHttpClient; + +public class MainActivity extends AppCompatActivity { + + private static final String TAG = "MainActivity"; + + PlayerView playerView; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + Context ctxt = this.getApplicationContext(); + + String storagePath = ctxt.getFilesDir().getAbsolutePath(); + + long nwid = Long.parseLong("0123456789abcdef", 16); + String remoteAddr = "10.147.19.37"; + int port = 8090; + String videoUri = "http://10.147.19.37:8090/sample"; + + + // ZeroTier setup + + ZeroTierNode node = new ZeroTierNode(); + node.initFromStorage(storagePath); + node.start(); + + // + // NOTE: This is demo code and waiting on main thread is not recommended + // + Log.d(TAG, "Waiting for node to come online..."); + while (! node.isOnline()) { + ZeroTierNative.zts_util_delay(50); + } + Log.d(TAG, "Node ID: " + String.format("%010x", node.getId())); + Log.d(TAG, "Joining network..."); + node.join(nwid); + Log.d(TAG, "Waiting for network..."); + while (! node.isNetworkTransportReady(nwid)) { + ZeroTierNative.zts_util_delay(50); + } + Log.d(TAG, "Joined"); + + + // Socket logic + + OkHttpClient client = new OkHttpClient.Builder() + .socketFactory(new ZeroTierSocketsSocketFactory(remoteAddr, port)) + .build(); + + assert client instanceof Call.Factory; + Call.Factory callFactory = (Call.Factory) client; + + OkHttpDataSource.Factory okhttpDataSourceFactory = + new OkHttpDataSource.Factory(callFactory); + + DefaultDataSource.Factory dataSourceFactory = + new DefaultDataSource.Factory( + ctxt, okhttpDataSourceFactory); + + ExoPlayer player = new ExoPlayer.Builder(ctxt) + .setMediaSourceFactory( + new DefaultMediaSourceFactory(ctxt).setDataSourceFactory(dataSourceFactory)) + .build(); + + playerView = findViewById(R.id.player_view); + + // Bind the player to the view. + playerView.setPlayer(player); + + // Build the media item. + + MediaItem mediaItem = MediaItem.fromUri(videoUri); + // Set the media item to be played. + player.setMediaItem(mediaItem); + // Prepare the player. + player.prepare(); + // Start the playback. + player.play(); + + } +} diff --git a/exoplayerclient/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/exoplayerclient/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/exoplayerclient/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/exoplayerclient/app/src/main/res/drawable/ic_launcher_background.xml b/exoplayerclient/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/exoplayerclient/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/exoplayerclient/app/src/main/res/layout/activity_main.xml b/exoplayerclient/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..d74d431 --- /dev/null +++ b/exoplayerclient/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,14 @@ + + + + + + \ No newline at end of file diff --git a/exoplayerclient/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/exoplayerclient/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/exoplayerclient/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/exoplayerclient/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/exoplayerclient/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/exoplayerclient/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/exoplayerclient/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/exoplayerclient/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000..c209e78 Binary files /dev/null and b/exoplayerclient/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/exoplayerclient/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/exoplayerclient/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000..b2dfe3d Binary files /dev/null and b/exoplayerclient/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/exoplayerclient/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/exoplayerclient/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000..4f0f1d6 Binary files /dev/null and b/exoplayerclient/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/exoplayerclient/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/exoplayerclient/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000..62b611d Binary files /dev/null and b/exoplayerclient/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/exoplayerclient/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/exoplayerclient/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000..948a307 Binary files /dev/null and b/exoplayerclient/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/exoplayerclient/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/exoplayerclient/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..1b9a695 Binary files /dev/null and b/exoplayerclient/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/exoplayerclient/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/exoplayerclient/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000..28d4b77 Binary files /dev/null and b/exoplayerclient/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/exoplayerclient/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/exoplayerclient/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9287f50 Binary files /dev/null and b/exoplayerclient/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/exoplayerclient/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/exoplayerclient/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000..aa7d642 Binary files /dev/null and b/exoplayerclient/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/exoplayerclient/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/exoplayerclient/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9126ae3 Binary files /dev/null and b/exoplayerclient/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/exoplayerclient/app/src/main/res/values-night/themes.xml b/exoplayerclient/app/src/main/res/values-night/themes.xml new file mode 100644 index 0000000..b8e668f --- /dev/null +++ b/exoplayerclient/app/src/main/res/values-night/themes.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/exoplayerclient/app/src/main/res/values/colors.xml b/exoplayerclient/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..c8524cd --- /dev/null +++ b/exoplayerclient/app/src/main/res/values/colors.xml @@ -0,0 +1,5 @@ + + + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/exoplayerclient/app/src/main/res/values/strings.xml b/exoplayerclient/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..59b5c87 --- /dev/null +++ b/exoplayerclient/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + ZeroTier Sockets Android ExoPlayer client + \ No newline at end of file diff --git a/exoplayerclient/app/src/main/res/values/themes.xml b/exoplayerclient/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..a7ba1c5 --- /dev/null +++ b/exoplayerclient/app/src/main/res/values/themes.xml @@ -0,0 +1,9 @@ + + + + +