Skip to content
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

collaborative editing #33

Open
eilseq opened this issue Apr 10, 2022 · 18 comments
Open

collaborative editing #33

eilseq opened this issue Apr 10, 2022 · 18 comments
Labels
feature completely new feature

Comments

@eilseq
Copy link

eilseq commented Apr 10, 2022

I think would be interesting considering an additional package to the structure that provides real-time audio and data communication. A typical use case would be a session of multiple players collaborating on the same session.

With this application in mind, I think the library should allow a user to initiate a public session and invite other players using the generated link. This means that the library would be in charge of defining a session and return a URL invitation.

The negotiation between peers can happen without a server, despite of the topology. There are few libraries that makes this process very easy. They take over most of the required coordination to instantiate a p2p stream, reducing a lot the amount of code needed to define a real-time session.

Suggested: https://github.com/peers/peerjs

@felixroos
Copy link
Collaborator

We definitely need collaborative editing! This issue should possibly wait for #28 , as it is tightly coupled with the editor implementation. Omicron666 also mentioned we could use yjs, which is also used by glicol and gibber. There are already codemirror 6 bindings in the making.

@eilseq
Copy link
Author

eilseq commented Apr 11, 2022

Perhaps we can start with audio streaming. The main goal would be to capture and stream external sources, eventually driven but the underline collaborative code. This is a typical use case in ensembles based on external midi gear or additional SynthDefs in supercollider. I would say that in terms of architecture, might also be a good starting point.

@felixroos
Copy link
Collaborator

Audio streaming alone would be nice too, but we should keep in mind that if we add collaborative editing later, it would ideally run through the same p2p connection as the audio (?). I imagine collaborative editing can be tricky to get right when writing from scratch (opposed to using readymade solution like yjs), but I might be wrong. At least something to be aware of..

@eilseq
Copy link
Author

eilseq commented Apr 11, 2022

It makes sense, but I still wouldn't prioritise the collaborative editor over the underlying real-time communication strategy. For example: if instead of PeerJS we use y-webrtc I think we would be able to retrieve some sort of session context that y-* packages rely on.

Main example for y-codemirror, clearly expose a provider based on webrtc:

import * as Y from 'yjs'
import { CodemirrorBinding } from 'y-codemirror'
import { WebrtcProvider } from 'y-webrtc'
import CodeMirror from 'codemirror'

const ydoc = new Y.Doc()
const provider = new WebrtcProvider('codemirror-demo-room', ydoc)
const yText = ydoc.getText('codemirror')
const yUndoManager = new Y.UndoManager(yText)

const editor = CodeMirror(editorDiv, {
  mode: 'javascript',
  lineNumbers: true
})

const binding = new CodemirrorBinding(yText, editor, provider.awareness, { yUndoManager })

In the class WebrtcProvider, the peer field is an instance of Peer from the simple-peer library. According to this example, Peer have an easy entry point for attaching a media stream to an existing instance. In the context of WebrtcProvider, that instance would be located under provider.peer:

var Peer = require('simple-peer')

// get video/voice stream
navigator.mediaDevices.getUserMedia({
  video: true,
  audio: true
}).then(gotMedia).catch(() => {})

function gotMedia (stream) {
  var peer1 = new Peer({ initiator: true, stream: stream })
  var peer2 = new Peer()

  peer1.on('signal', data => {
    peer2.signal(data)
  })

  peer2.on('signal', data => {
    peer1.signal(data)
  })

  peer2.on('stream', stream => {
    // got remote video stream, now let's show it in a video tag
    var video = document.querySelector('video')

    if ('srcObject' in video) {
      video.srcObject = stream
    } else {
      video.src = window.URL.createObjectURL(stream) // for older browsers
    }

    video.play()
  })
}

@felixroos
Copy link
Collaborator

great! looks like compatibility is given with minor adjustments. so nothing to worry with peerjs i guess.

@eilseq
Copy link
Author

eilseq commented Apr 11, 2022

Something like this would probably do. Can be a function provided by the package:

...
const ydoc = new Y.Doc()
const provider = new WebrtcProvider('codemirror-demo-room', ydoc)
const yText = ydoc.getText('codemirror')
const yUndoManager = new Y.UndoManager(yText)

...
export const bindCodemirrorEditor = (editor) => 
    new CodemirrorBinding(yText, editor, provider.awareness, { yUndoManager })

@felixroos felixroos added the enhancement improves an existing feature label Apr 24, 2022
@eilseq eilseq removed their assignment Sep 9, 2022
@eilseq
Copy link
Author

eilseq commented Sep 9, 2022

I'm un-assigning myself. Due to conflict of interest with my current position I cannot publish open-source code related to this particular subject.

@felixroos
Copy link
Collaborator

@boourns
Copy link

boourns commented Mar 1, 2023

I use yjs on sequencer.party for syncronizing any text documents, as well as the state of each WAM plugin. It is stable, in that when calling the functions, they return as expected and do what you would expect.

However, because every document edit is saved for reversability, documents grow in size quickly and subsequent edits begin to take longer and longer. This quickly collides with audio scheduling on the main thread and you will have sequencer skips every time the document is updated.

The solution for me was to move all yjs calls to their own worker thread. I used and enjoyed https://threads.js.org.

@felixroos
Copy link
Collaborator

I use yjs on sequencer.party for syncronizing any text documents, as well as the state of each WAM plugin. It is stable, in that when calling the functions, they return as expected and do what you would expect.

However, because every document edit is saved for reversability, documents grow in size quickly and subsequent edits begin to take longer and longer. This quickly collides with audio scheduling on the main thread and you will have sequencer skips every time the document is updated.

The solution for me was to move all yjs calls to their own worker thread. I used and enjoyed https://threads.js.org.

good to know, thanks for the hint. there is also comlink

@felixroos felixroos changed the title WebRTC Package: share audio and real-time data collaborative editing Mar 9, 2023
@felixroos
Copy link
Collaborator

just dumping this from chat here, so it won't get forgotten

yaxu — 03/01/2023 11:57 AM
Just thinking about collaborative editing again. I think this is the state of things:

froos — 03/01/2023 12:02 PM
sharing a single source of truth with multiple people might only be usable with block based evaluation
so either use the flok / estuary approach of one editor per user oder make sure you can evaluate individual blocks
I kind of like the idea of having a single editor instead of multiple
it would still be cool to work a bit on the flok integration

yaxu — 03/01/2023 12:45 PM
yes agreed on single editor + block-based eval would be nice.. although there could be a kind of hybrid like feedforward where blocks are automatically treated as separate patterns by the editor.. so can act a bit like separate editors (can be muted, visualised separately etc)

froos — 03/01/2023 12:53 PM
jep.. I don't have a clear picture of how it should work. it should generally be avoided to accidentally evaluate the stuff another person is writing at the moment

yaxu — 03/01/2023 12:56 PM
yes unless there is some kind of traffic light system, where instead of evaluating, you indicate you are ready for the next evaluation.. but that doesn't happen until everyone is ready
not sure how practical that would be and yep block-based probably better

@felixroos felixroos added feature completely new feature and removed enhancement improves an existing feature labels Mar 29, 2023
@jarmitage
Copy link
Contributor

@felixroos
Copy link
Collaborator

This looks pretty slick: https://github.com/automerge/automerge-codemirror

Relevant thread: https://twitter.com/geoffreylitt/status/1722334532546810089

automerge looks cool, seems it's a more general purpose implementation of crdt, yjs is probably still most optimized for collaborative editing. thread: yjs/yjs#145

@felixroos
Copy link
Collaborator

and btw to keep this issue updated: https://next.flok.cc now supports co coding multiple strudel instances

@yaxu
Copy link
Member

yaxu commented Jan 12, 2024

I'm un-assigning myself. Due to conflict of interest with my current position I cannot publish open-source code related to this particular subject.

Hi @eilseq, just a note that I'm planning on re-implement your mininotation PRs in the next days. Please let me know if your COI has changed and if you'd prefer to redo them yourself. In any case, thanks a lot for the contributions!

@eilseq
Copy link
Author

eilseq commented Jan 15, 2024

Hi @yaxu! It did change recently and I could join again, but has been some time since I've worked on this.
Feel free to take over! I will find some other issue to assist with :)

@yaxu
Copy link
Member

yaxu commented Jan 15, 2024

Hi @eilseq, welcome back :) I should finish up my PR this evening.

@felixroos
Copy link
Collaborator

ref #943

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature completely new feature
Projects
None yet
Development

No branches or pull requests

5 participants