-
Notifications
You must be signed in to change notification settings - Fork 57
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Creality K2: custom WebRTC feed #452
Comments
I've tried them, none of them seem to work for the Creality K2. Happy to provide whatever information will help. |
Can you provide me the log files? |
Hey @jamincollins If later is the case, I will need to add a custom WebRtc Manager to support their handshake format. |
The camera is a completely stand alone app/binary on the K2, Here's the recording you requested, but it amounts to the same thing I detailed above: |
I will look into it. If it's really just these three requests it should be an easy thing to add. |
It should be, the only other possible niggle may be scaling the feed you get, but I suspect that's already handled in your existing code. |
Just wanted to check in on this. |
Hey @jamincollins I will just add a new type that can handle this. |
I have triggered a new build on codemagic. |
Can you test the new creality apk on the release page: https://github.com/Clon1998/mobileraker/releases/tag/android2.8.4 |
@jamincollins can you give me feedback if the new webcam type is working? I tried it with my standard klipper printer and it seems like the mentioned path is also available. However, my camera-streamer responds with a |
Happy to test this on iPhone. Oddly enough Safari, Chrome, and Firefox on MacOS all show the stream when accessing the :8000 URL directly. Ditto Safari on iPhone. |
Eure, I just added the build to TestFlight which is now awaiting approval from apple. So it might take up to 24hrs |
Sorry for the delay. I just tested this. I see the I get:
|
Can you provide me the device logs |
Also try to include the "call/webrtc_local" in the webcam url |
|
I have the modification from the script at https://github.com/jamincollins/k2-improvements |
No it should not, it doesn't alter anything about the camera feed on |
You did not set the correct cam path. |
Not sure how the relative is going to find the right port, but here they are: |
Here's the changes that were made to Fluidd if that perhaps helps: |
Seems like you're cam config within mobileraker is still wrong as both versions still use the same URI: |
http://192.168.123.39:8000/ |
For nearly all webcam config setups I made them as customizable as possible. That's why for the K2 cam I once again did not include code to append the relative path to its call url. The error is a different one again. Can you provide me the log file of the app so I can have a look where the type error is in my code. |
I've provided this version and log file |
Thanks, |
@jamincollins please give the creality-webrtc-v2.apk a try. |
@jamincollins creality-webrtc-v3.apk. Ensured I actually use the SDP constraints I defined lol.... |
Now it continually says "Trying to connect...": |
Okay, I will verify that tomorrow. Might be that the webrtc lib I am using simply does not support it. |
Maybe I am blind but I can not find what else besides a platform/lib limitation is the error now: /*
* Copyright (c) 2023-2025. Patrick Schmidt.
* All rights reserved.
*/
import 'dart:async';
import 'dart:convert';
import 'package:common/exceptions/mobileraker_exception.dart';
import 'package:common/util/logger.dart';
import 'package:dio/dio.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart';
import 'data/webrtc_connection_event.dart';
import 'webrtc_manager.dart';
class CrealityRtcManager implements WebRtcManager {
final Map<String, dynamic> offerSdpConstraints = {
'mandatory': {
'OfferToReceiveAudio': false,
'OfferToReceiveVideo': true,
},
'optional': [],
};
final List<Map<String, String>> iceServers = [
{'url': 'stun:stun.l.google.com:19302'}
];
CrealityRtcManager({required Dio dio, required Uri camUri})
: _camUri = camUri,
_dio = dio;
final Dio _dio;
final Uri _camUri;
final RTCVideoRenderer _localRenderer = RTCVideoRenderer();
String? _remoteId;
RTCPeerConnection? _pc;
final StreamController<WebRtcConnectionEvent> _streamController = StreamController();
@override
Stream<WebRtcConnectionEvent> get stream => _streamController.stream;
RTCPeerConnectionState? __connectionState;
set _connectionState(RTCPeerConnectionState state) {
if (state == __connectionState) return;
logger.i(
'[CrealityRtcManager#${identityHashCode(this)}($_camUri)] RTC _onConnectionState: $__connectionState -> $state');
__connectionState = state;
WebRtcConnectionEvent event = switch (state) {
RTCPeerConnectionState.RTCPeerConnectionStateClosed => WebRtcConnectionEventClosed(),
RTCPeerConnectionState.RTCPeerConnectionStateFailed => WebRtcConnectionEventFailed(),
RTCPeerConnectionState.RTCPeerConnectionStateDisconnected => WebRtcConnectionEventDisconnected(),
RTCPeerConnectionState.RTCPeerConnectionStateNew => WebRtcConnectionEventNew(),
RTCPeerConnectionState.RTCPeerConnectionStateConnecting => WebRtcConnectionEventConnecting(),
RTCPeerConnectionState.RTCPeerConnectionStateConnected => WebRtcConnectionEventConnected(_localRenderer),
};
if (!_streamController.isClosed) _streamController.add(event);
}
@override
Future<void> startCam() async {
logger.i('[CrealityRtcManager#${identityHashCode(this)}($_camUri)] Starting Camera connection');
if (__connectionState == RTCPeerConnectionState.RTCPeerConnectionStateConnected) {
logger.i('[CrealityRtcManager#${identityHashCode(this)}($_camUri)] Camera already connected');
return;
}
if (__connectionState == RTCPeerConnectionState.RTCPeerConnectionStateConnecting) {
logger.i('[CrealityRtcManager#${identityHashCode(this)}($_camUri)] Camera already connecting');
return;
}
if (__connectionState == RTCPeerConnectionState.RTCPeerConnectionStateNew) {
logger.i('[CrealityRtcManager#${identityHashCode(this)}($_camUri)] Camera already new');
return;
}
try {
// Make sure to reset the connection state to provide a clean UI state!
_connectionState = RTCPeerConnectionState.RTCPeerConnectionStateNew;
await _pc?.dispose();
await _localRenderer.initialize();
_pc = await _setupPeerConnection();
// Submit to http://k2:8000/call/webrtc_local
final answer = await _sendOffer(_pc!);
await _handleAnswer(answer);
logger.i('[CrealityRtcManager#${identityHashCode(this)}($_camUri)] Completed webrtc offer and answer sequence');
} catch (e, s) {
logger.w('[CrealityRtcManager#${identityHashCode(this)}($_camUri)] Error while trying to start WebRTC cam', e);
_connectionState = RTCPeerConnectionState.RTCPeerConnectionStateFailed;
if (!_streamController.isClosed) _streamController.addError(e, s);
}
}
@override
Future<void> stopCam() async {
logger.i('[CrealityRtcManager#${identityHashCode(this)}($_camUri)] Stopping PeerConnection');
return _pc?.close();
}
@override
void toggleAudio([bool? toggleValue]) {
final next = toggleValue ?? !_localRenderer.muted;
_localRenderer.srcObject?.getAudioTracks().forEach((t) => t.enabled = next);
}
Future<RTCPeerConnection> _setupPeerConnection() async {
logger.i('[CrealityRtcManager#${identityHashCode(this)}($_camUri)] Setting up PeerConnection');
return await createPeerConnection({
'sdpSemantics': 'unified-plan',
'iceServers': iceServers,
}, offerSdpConstraints)
..onTrack = _onTrack
..onIceCandidate = _onIceCandidate
..onConnectionState = _onConnectionState
..onIceConnectionState = _onIceConnectionState
..onSignalingState = _onSignalingState
..addTransceiver(
kind: RTCRtpMediaType.RTCRtpMediaTypeVideo,
init: RTCRtpTransceiverInit(direction: TransceiverDirection.SendRecv));
}
Future<Response<String>> _sendOffer(RTCPeerConnection pc) async {
final offer = await _pc!.createOffer(offerSdpConstraints);
await _pc!.setLocalDescription(offer);
logger.i('[CrealityRtcManager#${identityHashCode(this)}($_camUri)] Sending offer to cam at $_camUri');
try {
final data = {
'type': 'offer',
'sdp': offer.sdp,
};
final encodedData = btoa(data);
logger.i('[CrealityRtcManager#${identityHashCode(this)}($_camUri)] Sending offer to cam: $encodedData');
final response = await _dio.postUri<String>(
_camUri,
options: Options(contentType: 'text/plain'),
data: encodedData,
);
logger.i('[CrealityRtcManager#${identityHashCode(this)}($_camUri)] Received answer from cam.');
return response;
} catch (e) {
logger.w(
'[CrealityRtcManager#${identityHashCode(this)}($_camUri)] Caught exception while sending offer to server',
e,
);
rethrow;
}
}
String btoa(Map<String, dynamic> data) {
final jsonData = jsonEncode(data);
final b64Data = base64Encode(utf8.encode(jsonData));
return b64Data;
}
Future<void> _handleAnswer(Response<String> answer) async {
logger.i(
'[CrealityRtcManager#${identityHashCode(this)}($_camUri)] Handling answer from cam: Code:${answer.statusCode}, ${answer.data}');
if (answer.data == null) {
logger.w('[CrealityRtcManager#${identityHashCode(this)}($_camUri)] Received empty answer from server');
throw MobilerakerException('Received empty answer from server. Please contact the developer.');
}
try {
final base64decode = base64Decode(answer.data!);
final jsonData = utf8.decode(base64decode);
final data = jsonDecode(jsonData) as Map<String, dynamic>;
switch (data) {
case {'type': != 'answer'}:
logger.w(
'[CrealityRtcManager#${identityHashCode(this)}($_camUri)] Received invalid answer type "${data['type']}" from server');
throw MobilerakerException(
'Received invalid answer type "${data['type']}" from server. Please contact the developer.');
case {'sdp': == null}:
logger.w(
'[CrealityRtcManager#${identityHashCode(this)}($_camUri)] Received invalid answer from server, missing "sdp"');
throw MobilerakerException('Received invalid answer sdp from server. Please contact the developer.');
case {'type': 'answer', 'sdp': String()}:
await _pc!.setRemoteDescription(RTCSessionDescription(data['sdp'], 'answer'));
break;
default:
logger
.w('[CrealityRtcManager#${identityHashCode(this)}($_camUri)] Received invalid answer from server: $data');
throw MobilerakerException('Received invalid answer from server. Please contact the developer.');
}
} catch (e, s) {
logger.w(
'[CrealityRtcManager#${identityHashCode(this)}($_camUri)] Caught exception while handling answer from server',
e,
s,
);
rethrow;
}
}
void _onTrack(RTCTrackEvent event) {
logger.i('[CrealityRtcManager#${identityHashCode(this)}($_camUri)] Received RTCTrackEvent');
if (_streamController.isClosed) return;
if (event.track.kind == 'video' && event.streams.isNotEmpty) {
_localRenderer.srcObject?.dispose();
_localRenderer.srcObject = event.streams.first;
// Disable audio by default
toggleAudio(false);
}
}
void _onIceCandidate(RTCIceCandidate event) async {
logger.i('[CrealityRtcManager#${identityHashCode(this)}($_camUri)] Received a new ICE event');
if (_streamController.isClosed) return;
if (event.candidate != null) return;
logger.i('[CrealityRtcManager#${identityHashCode(this)}($_camUri)] Received a new ICE event with null candidate');
// Note that as of 25.11.23 Ice candidates can not be sent to the server
try {
var localDescription = await _pc!.getLocalDescription();
final data = {
'type': 'offer',
'sdp': localDescription!.sdp,
};
logger.i('[CrealityRtcManager#${identityHashCode(this)}($_camUri)] Sending new offer to cam after ICE event');
final response = await _dio.postUri<String>(
_camUri,
options: Options(contentType: 'text/plain'),
data: btoa(data),
);
await _handleAnswer(response);
} catch (e) {
logger.w(
'[CrealityRtcManager#${identityHashCode(this)}($_camUri)] Caught exception while sending ICECandidate to server',
e);
// Some versions of the cam server do not support ICE candidates
}
}
void _onIceConnectionState(RTCIceConnectionState event) {
logger.i('[CrealityRtcManager#${identityHashCode(this)}($_camUri)] RTC onIceConnectionState: $event');
}
void _onSignalingState(RTCSignalingState state) {
logger.i('[CrealityRtcManager#${identityHashCode(this)}($_camUri)] RTC onSignalingState: $state');
}
void _onConnectionState(RTCPeerConnectionState event) {
_connectionState = event;
}
@override
void dispose() {
stopCam().ignore();
_localRenderer.dispose().ignore();
_streamController.close().ignore();
}
} |
Feature Request
Problem Description
So, the Creality K2 has something of an odd/custom camera feed.
The camera is available on http://printer_ip:8000/
I can't seem to find a way in the current Mobileraker UI to reference this camera.
Proposed Solution
Copy the logic of ether go2rtc or camerastreamer webrtc code and call to
/call/webrtc_local
and thats itAlternatives Considered
Additional Context
Here is the script from the above referenced page that shows the call to
/call/webrtc_local
The text was updated successfully, but these errors were encountered: