• Activity
  • Votes
  • Comments
  • New
  • All activity
    1. 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
    2. 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 the COMMIT_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 only a single file added, insert Add <filename>|

      • If a TODO added to Readme.org, insert ; TODO <headline>|

      • If a TODO was DONE, insert ; DONE <headline>|

      • If the files are Readme.org and Readme.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 type C-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
    3. 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
    4. 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
    5. 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...

      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 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 get M paths, each going from one city to another. Each path has k traffic controls. They're not that much effective, but the less of them you have to pass, the better. Find path from city A to city B, so the maximum number of traffic controls between any two cities is minimal. City A is always the first one (0) and city B 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 like 1 2 6, where 1 is id of first city and 2 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 controls 1 5 1. First example would have output 4, the second one would have output 5.

      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 or 0 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

      Special collection to speed up algorithm

      13 votes
    6. 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, or false 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
    7. 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
    8. 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