1
0
mirror of git://f0xx.org/android_cast synced 2026-07-29 05:17:39 +03:00
Signed-off-by: Anton Afanasyeu <a.afanasieff@gmail.com>
This commit is contained in:
Anton Afanasyeu
2026-05-18 18:26:28 +02:00
parent 8f991ad79b
commit 6d02a9ee82
12 changed files with 503 additions and 40 deletions

View File

@@ -27,7 +27,8 @@
<activity
android:name=".MainActivity"
android:exported="true">
android:exported="true"
android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize|keyboardHidden|density">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
@@ -37,7 +38,7 @@
<activity
android:name=".sender.SenderActivity"
android:exported="false"
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden" />
android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize|keyboardHidden|density" />
<activity
android:name=".receiver.ReceiverActivity"

View File

@@ -7,7 +7,7 @@ import android.content.Context;
public class AndroidCastApplication extends Application {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(CastLocaleHelper.attach(base));
super.attachBaseContext(base);
}
@Override

View File

@@ -49,6 +49,8 @@ public class ReceiverCastService extends Service {
private static final String TAG = "ReceiverCastService";
public static final String ACTION_START = "com.foxx.androidcast.RECEIVER_START";
public static final String ACTION_STOP = "com.foxx.androidcast.STOP_RECV";
public static final String ACTION_PLAYBACK_UI = "com.foxx.androidcast.RECV_PLAYBACK_UI";
public static final String EXTRA_PLAYBACK_UI_VISIBLE = "playback_ui_visible";
public static final String EXTRA_PIN = "pin";
public static final String EXTRA_ALLOW_AUDIO = "allow_audio";
@@ -82,6 +84,8 @@ public class ReceiverCastService extends Service {
private volatile boolean reconfiguring;
private volatile boolean awaitingKeyframe = true;
private boolean playbackLaunched;
/** True while {@link ReceiverPlaybackActivity} is in the foreground (started/stopped). */
private volatile boolean playbackUiVisible;
private int decoderWidth;
private int decoderHeight;
private boolean decoderActive;
@@ -174,6 +178,13 @@ public class ReceiverCastService extends Service {
stopSelfSafely();
return START_NOT_STICKY;
}
if (intent != null && ACTION_PLAYBACK_UI.equals(intent.getAction())) {
playbackUiVisible = intent.getBooleanExtra(EXTRA_PLAYBACK_UI_VISIBLE, false);
if (!playbackUiVisible) {
playbackLaunched = false;
}
return START_STICKY;
}
if (intent != null && ACTION_START.equals(intent.getAction())) {
String pin = intent.getStringExtra(EXTRA_PIN);
if (pin == null) {
@@ -575,6 +586,9 @@ public class ReceiverCastService extends Service {
/** Opens or refreshes fullscreen UI with robot overlay (listening / waiting for stream). */
private void openPlaybackAwaiting(int messageResId) {
if (!playbackUiVisible) {
return;
}
playbackLaunched = true;
Intent intent = new Intent(this, ReceiverPlaybackActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
@@ -584,12 +598,15 @@ public class ReceiverCastService extends Service {
}
private void notifyPlaybackDimensions(int width, int height) {
broadcastStreamStarted(width, height);
if (!playbackUiVisible) {
return;
}
Intent intent = new Intent(this, ReceiverPlaybackActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
intent.putExtra(ReceiverPlaybackActivity.EXTRA_WIDTH, width);
intent.putExtra(ReceiverPlaybackActivity.EXTRA_HEIGHT, height);
startActivity(intent);
broadcastStreamStarted(width, height);
}
private void closePlaybackUi() {
@@ -842,69 +859,77 @@ public class ReceiverCastService extends Service {
}
private void broadcastStatus(String s) {
int n = callbacks.beginBroadcast();
for (int i = 0; i < n; i++) {
broadcastToCallbacks(cb -> {
try {
callbacks.getBroadcastItem(i).onStatus(s);
cb.onStatus(s);
} catch (RemoteException ignored) {
}
}
callbacks.finishBroadcast();
});
}
private void broadcastAuthenticated(String name) {
int n = callbacks.beginBroadcast();
for (int i = 0; i < n; i++) {
broadcastToCallbacks(cb -> {
try {
callbacks.getBroadcastItem(i).onAuthenticated(name);
cb.onAuthenticated(name);
} catch (RemoteException ignored) {
}
}
callbacks.finishBroadcast();
});
}
private void broadcastStreamStarted(int w, int h) {
int n = callbacks.beginBroadcast();
for (int i = 0; i < n; i++) {
broadcastToCallbacks(cb -> {
try {
callbacks.getBroadcastItem(i).onStreamStarted(w, h);
cb.onStreamStarted(w, h);
} catch (RemoteException ignored) {
}
}
callbacks.finishBroadcast();
});
}
private void broadcastVideoRendering() {
int n = callbacks.beginBroadcast();
for (int i = 0; i < n; i++) {
broadcastToCallbacks(cb -> {
try {
callbacks.getBroadcastItem(i).onVideoRendering();
cb.onVideoRendering();
} catch (RemoteException ignored) {
}
}
callbacks.finishBroadcast();
});
}
private void broadcastStreamIdle() {
int n = callbacks.beginBroadcast();
for (int i = 0; i < n; i++) {
broadcastToCallbacks(cb -> {
try {
callbacks.getBroadcastItem(i).onStreamIdle();
cb.onStreamIdle();
} catch (RemoteException ignored) {
}
}
callbacks.finishBroadcast();
});
}
private void broadcastDisconnected() {
int n = callbacks.beginBroadcast();
for (int i = 0; i < n; i++) {
broadcastToCallbacks(cb -> {
try {
callbacks.getBroadcastItem(i).onDisconnected();
cb.onDisconnected();
} catch (RemoteException ignored) {
}
});
}
private interface CallbackAction {
void dispatch(ICastStatusCallback callback) throws RemoteException;
}
private void broadcastToCallbacks(CallbackAction action) {
synchronized (callbacks) {
int n = callbacks.beginBroadcast();
try {
for (int i = 0; i < n; i++) {
try {
action.dispatch(callbacks.getBroadcastItem(i));
} catch (RemoteException ignored) {
}
}
} finally {
callbacks.finishBroadcast();
}
}
callbacks.finishBroadcast();
}
private void releaseAll() {

View File

@@ -71,6 +71,8 @@ public class ReceiverPlaybackActivity extends DrawerHostActivity {
private ICastReceiverService receiverService;
private boolean bound;
private boolean autoStartListening;
private boolean listeningCancelled;
private final ICastStatusCallback.Stub statusCallback = new ICastStatusCallback.Stub() {
@Override
@@ -186,7 +188,8 @@ public class ReceiverPlaybackActivity extends DrawerHostActivity {
applyAspectRatio();
updatePipUi();
if (getIntent().getBooleanExtra(EXTRA_AUTO_START_LISTENING, false)) {
autoStartListening = getIntent().getBooleanExtra(EXTRA_AUTO_START_LISTENING, false);
if (autoStartListening) {
startListeningService();
showAwaitingOverlay(R.string.playback_listening);
} else if (getIntent().getBooleanExtra(EXTRA_SHOW_AWAITING, true)) {
@@ -291,11 +294,21 @@ public class ReceiverPlaybackActivity extends DrawerHostActivity {
@Override
protected void onStart() {
super.onStart();
setPlaybackUiVisible(true);
bindService(new Intent(this, ReceiverCastService.class), connection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onStop() {
setPlaybackUiVisible(false);
if (autoStartListening && !listeningCancelled && !castSessionActive && !streamRendering
&& bound && receiverService != null) {
try {
receiverService.stopReceiving();
listeningCancelled = true;
} catch (RemoteException ignored) {
}
}
if (bound && receiverService != null) {
try {
receiverService.unregisterCallback(statusCallback);
@@ -311,6 +324,13 @@ public class ReceiverPlaybackActivity extends DrawerHostActivity {
super.onStop();
}
private void setPlaybackUiVisible(boolean visible) {
Intent intent = new Intent(this, ReceiverCastService.class);
intent.setAction(ReceiverCastService.ACTION_PLAYBACK_UI);
intent.putExtra(ReceiverCastService.EXTRA_PLAYBACK_UI_VISIBLE, visible);
startService(intent);
}
private void onStreamLost() {
streamRendering = false;
showAwaitingOverlay(R.string.playback_stream_lost);

View File

@@ -955,14 +955,19 @@ public class ScreenCastService extends Service implements
}
private void broadcastStatus(String s) {
int n = callbacks.beginBroadcast();
for (int i = 0; i < n; i++) {
synchronized (callbacks) {
int n = callbacks.beginBroadcast();
try {
callbacks.getBroadcastItem(i).onStatus(s);
} catch (RemoteException ignored) {
for (int i = 0; i < n; i++) {
try {
callbacks.getBroadcastItem(i).onStatus(s);
} catch (RemoteException ignored) {
}
}
} finally {
callbacks.finishBroadcast();
}
}
callbacks.finishBroadcast();
}
private VideoEncoderSink.Callback buildVideoEncoderCallback(String mime) {

View File

@@ -44,7 +44,14 @@ public class SettingsStringArrayAdapter extends ArrayAdapter<String> {
@Override
@NonNull
public View getView(int position, View convertView, @NonNull ViewGroup parent) {
TextView tv = (TextView) super.getView(position, convertView, parent);
// Spinner may recycle dropdown rows (FrameLayout) into the closed row — never cast blindly.
TextView tv;
if (convertView instanceof TextView) {
tv = (TextView) convertView;
} else {
tv = (TextView) LayoutInflater.from(getContext())
.inflate(R.layout.spinner_item_settings, parent, false);
}
tv.setText(getItem(position));
tv.setAlpha(isEnabled(position) ? 1f : 0.45f);
return tv;