-
6 votes
-
State of WebRTC outside of major browsers
I've been trying to set up a reliable lightweight solution for high quality, low-latency webcam (v4l2) streaming from Linux server to browsers, allowing for small (1-5) number of concurrent...
I've been trying to set up a reliable lightweight solution for high quality, low-latency webcam (v4l2) streaming from Linux server to browsers, allowing for small (1-5) number of concurrent viewers.
The obvious choice here is WebRTC, which when used through browser APIs, works wonderfully. It has low latency and automatic quality adjustment depending on network performance.
I also checked out RTSP and RTMP, which are not supported without browser plugins. Next candidates were DASH and HLS, but while they provide high quality, they also have high latency.
For a while I used MPEG1 streaming through Websockets (using jsmpeg library), which worked and had low latency, but the video quality was bad.Back to WebRTC - It seems like reliable, lightweight and maintained projects are really hard to find. So far I've found a few WebRTC media servers, but they're overkill for my use case:
- Janus
- MediaSoup
- Kurento (unmaintained)
I also tried implementing this functionality using low level Gstreamer elements in Python using PyGObject, but that's proving to be rather complicated with a ton of extremely low level implementation details.
If anyone has tried doing something similar, I'd really like to hear what (if any) problems you had and if you found any sane solutions. Next thing on my list is using headless Chromium in combination with Puppeteer, but I'd really prefer more lightweight solutions.
9 votes -
Standardizing WASI: A system interface to run WebAssembly outside the web
8 votes -
Microsoft Defender ATP investigation unearths privilege escalation flaw in Huawei PCManager
5 votes -
Cleaning your GitHub profile with a simple Bash script
5 votes -
Make Emacs write (part of) your git commit messages
I was fed up with the chores of writing consistent git commit messages, so a while ago I started developing a hook in Emacs which I used with Magit (actually git-commit-mode) which uses some crude...
I was fed up with the chores of writing consistent git commit messages, so a while ago I started developing a hook in Emacs which I used with Magit (actually
git-commit-mode
) which uses some crude heuristics to fill out theCOMMIT_EDITMSG
buffer for me. Here is what it does (|
stands for the cursor):-
If only a single file modified, insert
<filename>: |
- If can figure out function name, insert
<filename> (<functionname>): |
- If can figure out function name, insert
-
If only a single file added, insert
Add <filename>|
-
If a
TODO
added toReadme.org
, insert; TODO <headline>|
-
If a
TODO
wasDONE
, insert; DONE <headline>|
-
If the files are
Readme.org
andReadme.org_archive
, and no new TODO's were added anywhere, insert; Archive DONE|
-
If the file is
.gitignore
, insert; Ignore |
-
If the file is
TAGS
, insert; Update TAGS|
I extend this when I find new cases where I repeatedly do the same thing. The code is below. It's probably a good idea to use it as a starting point and personalise it because this reflects how I like to write my commit messages (and I like pretending how they do it over at Emacs git repo). It is sloppy and probably buggy, but I don't think it can be destructive.
Final note: I can't figure out how to set this up so that after this takes effect, the buffer is marked as modified. I want to flip the modified bit so that in some cases I can just hit
C-c C-c
and go. But I need to modify the buffer somehow to commit in some cases (I just typeC-o
to open a new line in those cases). Here is the function:(defun gk-git-commit-mode-hook () "Set up git commit buffer." ;; If a single file is modified, prefix the message w/ it. (let ((modified-re "^# modified:") (new-re "^# new file:") (issue-re "^[+\\- ]\\*+ \\(TODO\\|DONE\\) ") current-defun filename addp onlyp issuep) (save-excursion (with-current-buffer "COMMIT_EDITMSG" (goto-char (point-min)) (re-search-forward "^# Changes to be committed:" nil t) (forward-line) (beginning-of-line) (cond ((looking-at modified-re) (re-search-forward ": " nil t) (setf filename (thing-at-point 'filename t))) ((looking-at new-re) (re-search-forward ": " nil t) (setf filename (thing-at-point 'filename t) addp t))) (setq onlyp (progn (forward-line) (not (or (looking-at modified-re) (looking-at new-re))))) (when (and onlyp (equal filename "Readme.org")) (goto-char (point-min)) (when-let* ((pos (re-search-forward issue-re nil t))) (setq issuep (progn (re-search-backward "\\*" nil t) (buffer-substring (1+ (point)) (line-end-position)))))) ;; Try to set ‘current-defun’. (when onlyp (save-excursion (goto-char (point-min)) ;; Error if not found, means verbose diffs ;; not enabled. (re-search-forward "^diff --git") (goto-char (line-beginning-position)) (let ((str (buffer-substring (point) (point-max))) (default-directory (expand-file-name ".."))) (with-temp-buffer (insert str) (diff-mode) (goto-char (point-min)) (setq current-defun (diff-current-defun)))))))) (if onlyp (cond ((and issuep (not addp)) (goto-char (point-min)) (insert ";" issuep)) ((equal filename "TAGS") (goto-char (point-min)) (insert "; Update TAGS")) ((equal filename ".gitignore") (goto-char (point-min)) (insert "; Ignore ")) (filename (goto-char (point-min)) (if addp (insert "Add " filename) (insert filename (if (and current-defun) (format " (%s)" current-defun) "") ": ")))) (when (and (equal filename "Readme.org") (save-excursion (goto-char (point-min)) (re-search-forward (concat modified-re " +Readme.org_archive") nil t)) (save-excursion (goto-char (point-min)) (re-search-forward "\\-\\*+ DONE" nil t)) (not (save-excursion (goto-char (point-min)) (re-search-forward "\\+\\*[\\+\\-] TODO" nil t)))) (goto-char (point-min)) (insert "; Archive DONE"))))) (add-hook 'git-commit-mode-hook #'gk-git-commit-mode-hook)
Hope you find it useful.
12 votes -
-
Better x86 Assembly Generation with Go
4 votes -
NPR New Packet Radio: IP over 430MHz Ham Radio, up to 500kbps, 20W RF. Extension for HSMM-Hamnet. 100% open-source.
9 votes -
How does compression work? A short explanation.
8 votes -
Redox OS 0.5.0
14 votes -
Rust: undefined behaviour in numeric conversions
6 votes -
Which OS do you use and how does it look?
Previous threads: What DE and distro do you use and why? What's your OS and how does it look? What does your desktop look like? What tools do you swear by? I've recently switched to Arch and...
Previous threads:
What DE and distro do you use and why?
What's your OS and how does it look?
What does your desktop look like? What tools do you swear by?
I've recently switched to Arch and today, it's finally done! You have no idea how hard it was to even turn it off. So I wanted to make another OS/screenshot thread, especially since it's been quite a long time since the last one, and Tildes population increased inbetween, so we might find something interesting again :-)
What's your OS, what do you love about it and how does it look?
26 votes -
Redis streams as a pure data structure
7 votes -
How I'm able to take notes in mathematics lectures using LaTeX and Vim
20 votes -
End-user programming
12 votes -
I need a good text editor on Windows 10
Disclaimer: I'm on Windows 10 I keep hopping between text editors because I can't find one that suits my needs. I need something simplistic, non-bloated, beginner friendly, open source, and...
Disclaimer: I'm on Windows 10
I keep hopping between text editors because I can't find one that suits my needs. I need something simplistic, non-bloated, beginner friendly, open source, and preferably with Dracula theme. I'll list off the ones that I've tried that I don't like too much:
- Atom
- Vim
- Emacs
- Notepad++
- VS Code
Honestly, I'd really like something that's like very similar to Sublime but FOSS. Is there one like that?
10 votes -
The RedMonk Programming Language Rankings: January 2019
4 votes -
Programmer migration patterns
9 votes -
Synthetic Sensors: Towards General-Purpose Sensing
4 votes -
Op amp on the Moon: Reverse-engineering a hybrid op amp module
5 votes -
Open Source Doesn’t Make Money Because It Isn’t Designed To Make Money
13 votes -
Coding for the Parallel Sega Saturn DSP
5 votes -
Here is where HPC is headed, according to developers and computer scientists in top supercomputing centers
4 votes -
Native File System API
6 votes -
Whats the most important alias or function in your bashrc file?
Hello new member here to Tildes! I just wanted to see what all people are putting in their bashrc file that makes them more productive?
40 votes -
SGI RealityEngine Paper
2 votes -
The 737Max and Software Engineering
6 votes -
Gnome 3.32 Released
7 votes -
Accessibility according to actual people with disabilities
6 votes -
Write Yourself A Git - write your own version control to help understand git internals
7 votes -
Sublime Text 3.2
9 votes -
A JavaScript-Free Frontend
16 votes -
Fighting uphill - the demoralizing state of accessibility on the web
8 votes -
Algorithms Allowed: a project that tracks usage of Google and Facebook assets in countries under US sanctions
6 votes -
NGINX has been acquired by F5 Networks
18 votes -
Announcing the release of sway 1.0
16 votes -
An All-Neural On-Device Speech Recognizer
7 votes -
An Async / Await Library for Emacs Lisp
3 votes -
[Presentation] Life of a Pixel
2 votes -
Why OpenBSD Rocks
16 votes -
Programming Challenge - Find path from city A to city B with least traffic controls inbetween.
Previous challenges Hi, it's been very long time from last Programming Challenge, and I'd like to revive the tradition. The point of programming challenge is to create your own solution, and if...
Hi, it's been very long time from last Programming Challenge, and I'd like to revive the tradition.
The point of programming challenge is to create your own solution, and if you're bored, even program it in your favourite programming language. Today's challenge isn't mine. It was created by ČVUT FIKS (year 5, season 2, challenge #4).
You need to transport plans for your quantum computer through Totalitatia. The problem is, that Totalitatia's government would love to have the plans. And they know you're going to transport the computer through the country. You'll receive number
N
, which denotes number of cities on the map. Then, you'll getM
paths, each going from one city to another. Each path hask
traffic controls. They're not that much effective, but the less of them you have to pass, the better. Find path from cityA
to cityB
, so the maximum number of traffic controls between any two cities is minimal. CityA
is always the first one (0
) and cityB
is always the last one (N-1
).Input format:
N M A1 B1 K1 A2 B2 K2 ...
On the first two lines, you'll get numbers N (number of cities) and M (number of paths). Than, on next
M
lines, you'll get definition of a path. The definition looks like1 2 6
, where1
is id of first city and2
is id of second city (delimited by a space). You can go from city 1 to city 2, or from city 2 to city 1. The third number (6
) is number of traffic controls.Output format:
Single number, which denotes maximum number of traffic controls encountered on one path.
Hint: This means, that path that goes via roads with numbers of traffic controls
4 4 4
is better than path via roads with numbers of traffic controls1 5 1
. First example would have output4
, the second one would have output5
.Example:
IN:
4 5 0 1 3 0 2 2 1 2 1 1 3 4 2 3 5
OUT:
4
Solution: The optimal path is either
0 2 1 3
or0 1 3
.Bonus
- Describe time complexity of your algorithm.
- If multiple optimal paths exist, find the shortest one.
- Does your algorithm work without changing the core logic, if the source city and the target city is not known beforehand (it changes on each input)?
- Do you use special collection to speed up minimum value search?
Hints
13 votes -
Programming Challenge: Anagram checking.
It's been over a week since the last programming challenge and the previous one was a bit more difficult, so let's do something easier and more accessible to newer programmers in particular. Write...
It's been over a week since the last programming challenge and the previous one was a bit more difficult, so let's do something easier and more accessible to newer programmers in particular. Write a function that takes two strings as input and returns
true
if they're anagrams of each other, orfalse
if they're not.Extra credit tasks:
- Don't consider the strings anagrams if they're the same barring punctuation.
- Write an efficient implementation (in terms of time and/or space complexity).
- Minimize your use of built-in functions and methods to bare essentials.
- Write the worst--but still working--implementation that you can conceive of.
24 votes -
USB4 will support Thunderbolt and double the speed of USB 3.2
20 votes -
Why open source projects don't charge (while keeping the code open)?
I'd gladly pay a reasonable price for professional packages/support for programs like Emacs/Melpa, Debian, and Xfce. As a user, I empathize with the complaints by developers that are constantly...
I'd gladly pay a reasonable price for professional packages/support for programs like Emacs/Melpa, Debian, and Xfce. As a user, I empathize with the complaints by developers that are constantly overworked. Even if this doesn't generate enough money to pay for everything, it might be enough to hire someone to handle the issues and communities, something that clearly drains their efforts, especially because programmers tend to prefer technical challenges rather than dealing with people.
I understand that many projects accept donations, but I think providing an actual reward (even if its something minimal, like an updated package instead of having to build it from source) might be a good way to get resources and avoid developer burndown.
11 votes -
ALOHAnet - The oft-forgotten Precusor to Wireless Communication
4 votes -
Code Quality Tip: Cyclomatic complexity in depth.
Preface Recently I briefly touched on the subject of cyclomatic complexity. This is an important concept for any programmer to understand and think about as they write their code. In order to...
Preface
Recently I briefly touched on the subject of cyclomatic complexity. This is an important concept for any programmer to understand and think about as they write their code. In order to provide a more solid understanding of the subject, however, I feel that I need to address the topic more thoroughly with a more practical example.
What is cyclomatic complexity?
The concept of "cyclomatic complexity" is simple: the more conditional branching and looping in your code, the more complex--and therefore the more difficult to maintain--that code is. We can visualize this complexity by drawing a diagram that illustrates the flow of logic in our program. For example, let's take the following toy example of a user login attempt:
<?php $login_data = getLoginCredentialsFromInput(); $login_succeeded = false; $error = ''; if(usernameExists($login_data['username'])) { $user = getUser($login_data['username']); if(!isDeleted($user)) { if(!isBanned($user)) { if(!loginRateLimitReached($user)) { if(passwordMatches($user, $login_data['password'])) { loginUser($user); $login_succeeded = true; } else { $error = getBadPasswordError(); logBadLoginAttempt(); } } else { $error = getLoginRateLimitError($user); } } else { $error = getUserBannedError($user); } } else { $error = getUserDeletedError($user); } } else { $error = getBadUsernameError($login_data['username']); } if($login_succeeded) { sendSuccessResponse(); } else { sendErrorResponse($error); } ?>
A diagram for this logic might look something like this:
+-----------------+ | | | Program Start | | | +--------+--------+ | | v +--------+--------+ +-----------------+ | | | | | Username +--->+ Set Error +--+ | Exists? | No | | | | | +-----------------+ | +--------+--------+ | | | Yes | | v | +--------+--------+ +-----------------+ | | | | | | | User Deleted? +--->+ Set Error +->+ | | Yes| | | +--------+--------+ +-----------------+ | | | No | | v | +--------+--------+ +-----------------+ | | | | | | | User Banned? +--->+ Set Error +->+ | | Yes| | | +--------+--------+ +-----------------+ | | | No | | v | +--------+--------+ +-----------------+ | | | | | | | Login Rate +--->+ Set Error +->+ | Limit Reached? | Yes| | | | | +-----------------+ | +--------+--------+ | | | No | | v | +--------+--------+ +-----------------+ | | | | | | |Password Matches?+--->+ Set Error +->+ | | No | | | +--------+--------+ +-----------------+ | | | Yes | | v | +--------+--------+ +----------+ | | | | | | | Login User +--->+ Converge +<--------+ | | | | +-----------------+ +---+------+ | | +-----------------+ | v +--------+--------+ | | | Succeeded? +-------------+ | | No | +--------+--------+ | | | Yes | | v v +--------+--------+ +--------+--------+ | | | | | Send Success | | Send Error | | Message | | Message | | | | | +-----------------+ +-----------------+
It's important to note that between nodes in this directed graph, you can find certain enclosed regions being formed. Specifically, each conditional branch that converges back into the main line of execution generates an additional region. The number of these distinct enclosed regions is directly proportional to the level of cyclomatic complexity of the system--that is, more regions means more complicated code.
Clocking out early.
There's an important piece of information I noted when describing the above example:
. . . each conditional branch that converges back into the main line of execution generates an additional region.
The above example is made complex largely due to an attempt to create a single exit point at the end of the program logic, causing these conditional branches to converge and thus generate the additional enclosed regions within our diagram.
But what if we stopped trying to converge back into the main line of execution? What if, instead, we decided to interrupt the program execution as soon as we encountered an error? Our code might look something like this:
<?php $login_data = getLoginCredentialsFromInput(); if(!usernameExists($login_data['username'])) { sendErrorResponse(getBadUsernameError($login_data['username'])); return; } $user = getUser($login_data['username']); if(isDeleted($user)) { sendErrorResponse(getUserDeletedError($user)); return; } if(isBanned($user)) { sendErrorResponse(getUserBannedError($user)); return; } if(loginRateLimitReached($user)) { logBadLoginAttempt($user); sendErrorResponse(getLoginRateLimitError($user)); return; } if(!passwordMatches($user, $login_data['password'])) { logBadLoginAttempt($user); sendErrorResponse(getBadPasswordError()); return; } loginUser($user); sendSuccessResponse(); ?>
Before we've even constructed a diagram for this logic, we can already see just how much simpler this logic is. We don't need to traverse a tree of if statements to determine which error message has priority to be sent out, we don't need to attempt to follow indentation levels, and our behavior on success is right at the very end and at the lowest level of indentation, where it's easily and obviously located at a glance.
Now, however, let's verify this reduction in complexity by examining the associated diagram:
+-----------------+ | | | Program Start | | | +--------+--------+ | | v +--------+--------+ +-----------------+ | | | | | Username +--->+ Send Error | | Exists? | No | Message | | | | | +--------+--------+ +-----------------+ | Yes | v +--------+--------+ +-----------------+ | | | | | User Deleted? +--->+ Send Error | | | Yes| Message | +--------+--------+ | | | +-----------------+ No | v +--------+--------+ +-----------------+ | | | | | User Banned? +--->+ Send Error | | | Yes| Message | +--------+--------+ | | | +-----------------+ No | v +--------+--------+ +-----------------+ | | | | | Login Rate +--->+ Send Error | | Limit Reached? | Yes| Message | | | | | +--------+--------+ +-----------------+ | No | v +--------+--------+ +-----------------+ | | | | |Password Matches?+--->+ Send Error | | | No | Message | +--------+--------+ | | | +-----------------+ Yes | v +--------+--------+ | | | Login User | | | +--------+--------+ | | v +--------+--------+ | | | Send Success | | Message | | | +-----------------+
Something should immediately stand out here: there are no enclosed regions in this diagram! Furthermore, even our new diagram is much simpler to follow than the old one was.
Reality is rarely simple.
The above is a really forgiving example. It has no loops, and loops are going to create enclosed regions that can't be broken apart so easily; it has no conditional branches that are so tightly coupled with the main path of execution that they can't be broken up; and the scope of functionality and side effects are minimal. Sometimes you can't break those regions up. So what do we do when we inevitably encounter these cases?
High cyclomatic complexity in your program as a whole is inevitable for sufficiently large projects, especially in a production environment, and your efforts to reduce it can only go so far. In fact, I don't recommend trying to remove all or even most instances of cyclomatic complexity at all--instead, you should just be keeping the concept in mind to determine whether or not a function, method, class, module, or other component of your system is accumulating technical debt and therefore in need of refactoring.
At this point, astute readers might ask, "How does refactoring help if the cyclomatic complexity doesn't actually go away?", and this is a valid concern. The answer to that is simple, however: we're hiding complexity behind abstractions.
To test this, let's forget about cyclomatic complexity for a moment and instead focus on simplifying the refactored version of our toy example using abstraction:
<?php function handleLoginAttempt($login_data) { if(!usernameExists($login_data['username'])) { sendErrorResponse(getBadUsernameError($login_data['username'])); return; } $user = getUser($login_data['username']); if(isDeleted($user)) { sendErrorResponse(getUserDeletedError($user)); return; } if(isBanned($user)) { sendErrorResponse(getUserBannedError($user)); return; } if(loginRateLimitReached($user)) { logBadLoginAttempt($user); sendErrorResponse(getLoginRateLimitError($user)); return; } if(!passwordMatches($user, $login_data['password'])) { logBadLoginAttempt($user); sendErrorResponse(getBadPasswordError()); return; } loginUser($user); sendSuccessResponse(); } $login_data = getLoginCredentialsFromInput(); handleLoginAttempt($login_data); ?>
The code above is functionally identical to our refactored example from earlier, but has an additional abstraction via a function. Now we can diagram this higher-level abstraction as follows:
+-----------------+ | | | Program Start | | | +--------+--------+ | | v +--------+--------+ | | | Attempt Login | | | +-----------------+
This is, of course, a pretty extreme example, but this is how we handle thinking about complex program logic. We abstract it down to the barest basics so that we can visualize, in its simplest form, what the program is supposed to do. We don't actually care about the implementation unless we're digging into that specific part of the system, because otherwise we would be so bogged down by the details that we wouldn't be able to reason about what our program is supposed to do.
Likewise, we can use these abstractions to hide away the cyclomatic complexity underlying different components of our software. This keeps everything clean and clutter-free in our head. And the more we do to keep our smaller components simple and easy to think about, the easier the larger components are to deal with, no matter how much cyclomatic complexity all of those components share as a collective.
Final Thoughts
Cyclomatic complexity isn't a bad thing to have in your code. The concept itself is only intended to be used as one of many tools to assess when your code is accumulating too much technical debt. It's a warning sign that you may need to change something, nothing more. But it's an incredibly useful tool to have available to you and you should get comfortable using it.
As a general rule of thumb, you can usually just take a glance at your code and assess whether or not there's too much cyclomatic complexity in a component by looking for either of the following:
- Too many loops and/or conditional statements nested within each other, i.e. you have a lot of indentation.
- Many loops in the same function/method.
It's not a perfect rule of thumb, but it's useful for at least 90% of your development needs, and there will inevitably be cases where you will prefer to accept some greater cyclomatic complexity because there is some benefit that makes it a better trade-off. Making that judgment is up to you as a developer.
As always, I'm more than willing to listen to feedback and answer any questions!
25 votes -
You Don’t Need All That Complex/Expensive/Distracting Infrastructure
14 votes -
How many dots can you see? Optical illusion in 18 lines of pure JavaScript - easy tutorial
6 votes -
Rich Felker, the creator of musl libc, asks the community for financial support via Patreon
9 votes -
Running a bakery on Emacs and PostgreSQL
4 votes