Minamp 1.2

- Transcoding: send client identities Plex has a profile for (unknown ones get
  400 "unable to find a matching profile"), with a fresh session id per variant
- Stream proxy: fetch transcodes once into a temp file and serve every player
  request from it, so reconnects no longer restart the transcode (503s, tracks
  cut short); original files still pass Range requests through
- Sign-in: remember the pending link code so it survives Minamp being closed
  while the code is entered elsewhere; add "I've entered it - check now"
- Diagnostics: in-app connection log (DiagLog) with a streaming test and save
  to SD card; tokens are never logged

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
xockoandClaude Opus 5.5 committed 2026-10-14 20:38:17 +02:00
1 parent 0a2ac901bd
commit a4b875ef34
12 files changed
+701 -196

No files matched your search

+87 -31
View File
@@ -47,7 +47,9 @@ public class SetupActivity extends BaseActivity {
findViewById(R.id.btn_signin).setOnClickListener(new View.OnClickListener() { public void onClick(View v) { startPin(); } });
findViewById(R.id.btn_manual).setOnClickListener(new View.OnClickListener() { public void onClick(View v) { show(manual); } });
findViewById(R.id.btn_pin_cancel).setOnClickListener(new View.OnClickListener() { public void onClick(View v) { pinSeq++; show(welcome); } });
findViewById(R.id.btn_pin_cancel).setOnClickListener(new View.OnClickListener() { public void onClick(View v) { pinSeq++; clearPendingPin(); show(welcome); } });
findViewById(R.id.btn_pin_check).setOnClickListener(new View.OnClickListener() { public void onClick(View v) { checkPinNow(pinSeq); } });
findViewById(R.id.btn_setup_log).setOnClickListener(new View.OnClickListener() { public void onClick(View v) { showDiagnostics(); } });
findViewById(R.id.btn_manual_back).setOnClickListener(new View.OnClickListener() { public void onClick(View v) { show(welcome); } });
findViewById(R.id.btn_manual_go).setOnClickListener(new View.OnClickListener() { public void onClick(View v) { connectManual(); } });
findViewById(R.id.btn_choose_back).setOnClickListener(new View.OnClickListener() {
@@ -60,7 +62,7 @@ public class SetupActivity extends BaseActivity {
loadSections(app.server, null);
} else if (mode == MODE_SERVER && account != null) {
loadServers(account);
} else {
} else if (!resumePendingPin()) {
show(welcome);
}
}
@@ -96,21 +98,21 @@ public class SetupActivity extends BaseActivity {
// ------------------------------------------------------------------ PIN sign-in
private int pinChecks;
private boolean pinCheckRunning;
private void startPin() {
showBusy("Contacting plex.tv…\n(the first secure connection takes a few seconds on this phone)");
clearPendingPin();
showBusy("Contacting plex.tv\u2026\n(the first secure connection takes a few seconds on this phone)");
final int my = ++pinSeq;
run(new Task<Plex.Pin>() {
public Plex.Pin work() throws Exception { return Plex.createPin(); }
public void done(Plex.Pin p) {
if (my != pinSeq) return;
pinId = p.id;
pinStarted = System.currentTimeMillis();
StringBuilder spaced = new StringBuilder();
for (int i = 0; i < p.code.length(); i++) { if (i > 0) spaced.append(' '); spaced.append(p.code.charAt(i)); }
pinCode.setText(spaced.toString());
pinStatus.setText("Waiting for you to link…");
show(pinPage);
pollPin(my);
// Remember the code: the phone may close Minamp while you type it in elsewhere.
app.prefs.edit().putLong("pinId", p.id).putString("pinCode", p.code)
.putLong("pinStart", System.currentTimeMillis()).commit();
showPin(p.id, p.code, System.currentTimeMillis());
}
public void fail(Exception e) {
if (my != pinSeq) return;
@@ -120,30 +122,84 @@ public class SetupActivity extends BaseActivity {
});
}
private void pollPin(final int my) {
/** Resume waiting for a code created earlier (Minamp was closed while the user typed it in). */
private boolean resumePendingPin() {
long id = app.prefs.getLong("pinId", 0);
String code = app.prefs.getString("pinCode", null);
long start = app.prefs.getLong("pinStart", 0);
if (id == 0 || code == null || System.currentTimeMillis() - start > 14 * 60 * 1000) {
clearPendingPin();
return false;
}
com.minamp.net.DiagLog.log("resuming sign-in code " + code);
showPin(id, code, start);
return true;
}
private void clearPendingPin() {
app.prefs.edit().remove("pinId").remove("pinCode").remove("pinStart").commit();
}
private void showPin(long id, String code, long started) {
pinId = id;
pinStarted = started;
pinChecks = 0;
StringBuilder spaced = new StringBuilder();
for (int i = 0; i < code.length(); i++) { if (i > 0) spaced.append(' '); spaced.append(code.charAt(i)); }
pinCode.setText(spaced.toString());
pinStatus.setText("Waiting for you to link\u2026");
show(pinPage);
int my = ++pinSeq;
checkPinNow(my);
}
@Override
protected void onResume() {
super.onResume();
// back from typing the code somewhere else: check straight away
if (pinPage != null && pinPage.getVisibility() == View.VISIBLE && !pinCheckRunning) checkPinNow(pinSeq);
}
private void schedulePinCheck(final int my) {
app.handler.postDelayed(new Runnable() {
public void run() {
if (my != pinSeq || destroyed) return;
if (System.currentTimeMillis() - pinStarted > 14 * 60 * 1000) {
pinStatus.setText("Code expired — go back and try again");
public void run() { checkPinNow(my); }
}, 3000);
}
private void checkPinNow(final int my) {
if (my != pinSeq || destroyed || pinCheckRunning) return;
long age = System.currentTimeMillis() - pinStarted;
if (age > 14 * 60 * 1000) {
pinStatus.setText("Code expired \u2014 press Cancel and sign in again");
clearPendingPin();
return;
}
pinCheckRunning = true;
run(new Task<String>() {
public String work() throws Exception { return Plex.checkPin(pinId); }
public void done(String token) {
pinCheckRunning = false;
if (my != pinSeq) return;
pinChecks++;
if (token == null) {
long left = (14 * 60 * 1000 - (System.currentTimeMillis() - pinStarted)) / 60000;
pinStatus.setText("Not linked yet \u00b7 checked " + pinChecks + "\u00d7 \u00b7 " + left + " min left");
schedulePinCheck(my);
return;
}
SetupActivity.this.run(new Task<String>() {
public String work() throws Exception { return Plex.checkPin(pinId); }
public void done(String token) {
if (my != pinSeq) return;
if (token == null) { pollPin(my); return; }
pinSeq++;
signedIn(token);
}
public void fail(Exception e) {
if (my != pinSeq) return;
pinStatus.setText("Retrying… (" + msg(e) + ")");
pollPin(my);
}
});
pinStatus.setText("Linked!");
pinSeq++;
clearPendingPin();
signedIn(token);
}
}, 2500);
public void fail(Exception e) {
pinCheckRunning = false;
if (my != pinSeq) return;
pinChecks++;
pinStatus.setText("Check " + pinChecks + " failed: " + msg(e) + " \u2014 retrying");
schedulePinCheck(my);
}
});
}
private void signedIn(final String account) {