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

More accurately record time spent consuming video media #261

Open
douglasg14b opened this issue Jan 7, 2019 · 57 comments · Fixed by ActivityWatch/aw-webui#262
Open

More accurately record time spent consuming video media #261

douglasg14b opened this issue Jan 7, 2019 · 57 comments · Fixed by ActivityWatch/aw-webui#262

Comments

@douglasg14b
Copy link

The Problem

One of the larger time-sinks today is video. Be that through streaming services like Netflix, Amazon Prime Video, or HBO. Media sites like YouTube, Twitch, or Vimeo. Or from downloaded or streamed media on players like VLC or MPV.

Activitywatch, unfortunately, fails to effectively record the time spent on these activities as it relies on mouse and keyboard input to determine activity. This means when watching a video, Activitywatch will mark the time as afk after a short while. Even though the user is present, and spending time on an activity at their device.

This was brought up in #186 which was marked as wontfix. I believe that this something that CAN be solved, and should be seriously considered given the amount of time that can be spent consuming media.

Afk time can be disabled, but this then pollutes the data. Users may go afk for a variety of times in a variety of applications or websites throughout the day, which could pollute the afk time to the point of video-specific time may no longer be useful.

Possible solutions

Note: Not all problems/disadvantages/pitfalls are meant to be solvable. I am including them for devils advocates sake and to foster a more robust discussion.

Application/Site Tagging

Compile a list of common media applications and websites. When the use goes afk on this site or application, mark the time as non-afk. This isn't technically tagging, but it could be setup to work in a tag-like way, which would make this into a very extendable solution for more than just videos.

Advantages
  • Easy to maintain
    • Add sites/applications as you see fit, or as the community points them out
  • Simple
Disadvantages/Pitfalls
  • Initial list will be a chore to create
    • Can always ask the community for assistance. You create the functionality, and get the community to fill it in with you curating.
  • Is not quite perfect.
  • What if the user is legitimately afk on a youtube page?
  • What if the user is afk on the homepage of a media site, and not on an actual video?
  • What if the user is afk on a video, and it's paused?
Enhancements

These are here to try and solve for some of the problems presented under disadvantages.

  • User-defined or user changeable lists and filters
    • The repo still maintains a list, but users have the ability to add items to theirs without waiting for a pull request to merge and a new release.
    • There are a variety of flavorful solutions to this that I will not go into
  • Combine with monitoring hardware to detect if a video might be paused

Monitoring Hardware

Monitor audio output to see if a video is playing.

Advantages
  • Simple. May not be easy to implement, but is a simple solution to the problem
Disadvantages/Pitfalls
  • What if the user is listening to music, like spotify, and is afk on a media player?
  • What if the user is playing music from a media site like YouTube?
  • Difficult to implement?
  • Monitoring multiple audio devices necessary?
Enhancements
  • Combine with site/application tagging

General Enhancements

These are enhancements that could apply to any solution, to increase accuracy and to enable the user to correct mismatches and errors.

User-Defined Lists & Filters

Let the user create/modify the list of sites/applications, and/or the patterns used to match them.

  • Takes burden off of developer to maintain accurate lists and patterns to meet everyone's specific needs
    • I would still expect than an "official" or pre-defined list of items and patterns would be bundled or downloadable
  • Some passionate users can open issues to merge items and patterns back into the repo with the dev curating them, or just to share them.

Tagging and Pattern Matching

This goes above and beyond, but would really turn this into a much more powerful tool.

Instead of just solving the video problem. Create an extendable solution that encompass the general problem category that the video problem is part of. This would be in the form of tagging, being able to automatically tag domains & applications with predefined or user defined tags. This can be facilitated with pattern matching lists, and depending on the data's schema/format could be applied to already existing data greatly enhancing it's utility.

As an example, time in VLC, YouTube, or Netflix could be tagged as video, which gives users the power to filter this time separately, combine it in reports, or to more easily correct collection errors.

This of course could be setup to be user-manageable, with points listed in the previous section.

Conclusion

I believe the ability to capture time spent consuming video-based media will have an impact on the future usability of this project as these sorts of services continue to expand and bring in more and more people. Solving this problem can not only provide a solution to this problem, but could also greatly enhance the utility and power of this application.

What are your thoughts? (please to not be automarking this as closed, this took some time and effort to create).

@ErikBjare
Copy link
Member

ErikBjare commented Jan 7, 2019

Thanks for taking the time to write all that, greatly appreciated!

I agree, and there is actually unmerged code that solves the issue for web-based media players through the use of the audible property on tabs: ActivityWatch/aw-webui#85

I'm personally pretty happy with the above solution, as it creates minimal complexity and works for the (what I suspect is) the most common ways people consume video on their computers today (YouTube, Netflix, other web-based players).

I'd love to give more thorough feedback on the options you mentioned, especially tagging, but I'm really busy with exams this week so it'll have to wait. In the meanwhile, check out the discussion in #95

@nicolae-stroncea
Copy link
Member

nicolae-stroncea commented Jan 9, 2019

This is an idea aimed towards video-playing apps, which is a big part of consuming media.

Make a separate watcher for the media. The watcher could be either just for the PC media software or for both(it would get the audible property from the web watchers).

Have a white-list of apps, check if they're on the screen. To count time on the media-watcher, you just count the time the app is on display. We have the same downside that @douglasg14b mentioned, i.e What if user really pauses the app and leaves whilst app is on the screen?

The solution:

Check if the computer is asleep or not. On both, Linux, Mac and Windows, the computer will usually not go to sleep if there is media(This would work for video, not sure about purely audio) playing. One thing to further investigate is whether the media software needs to be in full screen and playing for the computer not to go to sleep. Therefore, if there is a media app on the screen, and the computer is not asleep, we would log that as playing time for the app.

Following false positives would occur:

  • App is paused but still on the screen whilst user is not afk would be logged as active.(this will happen rarely happen, as users tend not to fill screen real-estate with a video app if it is not being used).
  • Computer never goes to sleep(this is also a fairly rare case), so media time would be logged even if video is paused.
  • Accuracy also depends on the time it takes for the OS to go to sleep following user inactivity, including media.

@douglasg14b
Copy link
Author

douglasg14b commented Jan 9, 2019

@nicolae-stroncea

Detecting active audio alongside a list of sites/apps would bring the accuracy up to a very acceptable level in my opinion. Either of them by themselves would be too riddled with false positives to be too terribly useful. There isn't much need to go fancier than that imho. This is something I went into in the initial post.

Active audio + afk + on youtube = watching video. It's not perfect, but much better than on youtube = watching video. As an example, I have 4 monitors, and I almost always have something playing when working, and will often click on that to pause it then leave for a while. Leaving myself afk with an active video player that isn't playing. Or even watching Netflix, pause it and leave for a while, it's the active window, but nothing is playing.

Letting users create their own pattern matching for sites will also bring up the accuracy as it lets them add sites/applications to the list of video apps/sites.

@nicolae-stroncea
Copy link
Member

nicolae-stroncea commented Jan 9, 2019

@douglasg14b

I agree that detecting audio would be the most accurate way of doing it. As you've stated, it is a complex solution. The solution I offered was meant as a less complex alternative (i think it's fair to say it would be easier to implement with the existing code, and might have a lot fewer edge cases than if we go into monitoring hardware), but at the sake of less accuracy. It ultimately depends on the amount of effort that will be put into the feature. If monitoring hardware to detect audio successfully will take too many man-hours to implement at the time being, I think the solution I suggested is a feasible proposal.

I disagree that it would be so riddled with false positives to not be useful. youtube is an active window && computer not on standby = watching video would be the more accurate representation. For your example, where you would watch Netflix, pause and leave for a while. Presumably, the computer would go on stand-by, at which point, the Netflix app would not be counted anymore for active media time.

@douglasg14b
Copy link
Author

douglasg14b commented Jan 9, 2019

I believe that relying on standby is an incorrect assumption for users of this library. How many people's devices that are not laptops go into standby within a few minutes of it being idle? Even plugged in laptops default to 30-60mins on Windows 10 in balanced mode, what about high performance mode? On desktops?. You're looking at 30m - 4h IF standby is even enabled, and their devices don't just turn off the screens and stay on. Nevermind most Linux users who probably don't use standby at all from what I've seen as it's usually not on by default for most desktop installs of common flavors

That's a lot of invalid data. If you're watching a movie, and you step away to do something (bathroom, cleaning, walk to dog, cooking, make coffee...etc) are most people actually gone long enough for their device to go into standby (60mins)?

That would also rely VERY heavily on the end users setup, which can vary wildly, especially when assuming that their power configuration is not set as the defaults. Assumptions on user device configuration shouldn't be made unless there is data to back it up. Which is why I believe it will be more inaccurate, and potentially worse than just not recording it at all as it currently does.


Thankfully this is a FOSS project, so man-hours isn't as much of a concern as if this was an in-company product with expenses and wages to worry about. It's still relevant, but at least in my projects, I don't consider time to implement as a deciding factor for features or compatibility unless a solid and usable drop-in is available.

@dynamiclover
Copy link

Has anyone investigated integrating with media players through the same mechanisms as last.fm/audio scrobblers?

@jtrakk
Copy link

jtrakk commented Aug 21, 2019

Another idea would be to take a screenshot and if the screen has changed, consider it active (not afk).

@johan-bjareholt
Copy link
Member

johan-bjareholt commented Aug 21, 2019

Has anyone investigated integrating with media players through the same mechanisms as last.fm/audio scrobblers?

@dynamiclover We have aw-watcher-spotify as an experiment https://github.com/activitywatch/aw-watcher-spotify

Another idea would be to take a screenshot and if the screen has changed, consider it active (not afk).

@jtrakk Two issues with this

  • Most users have a clock in their taskbar on their computer, this will change at least every minute. Then you you could argue that you need a minimum amount of pixels to change, but that still becomes inaccurate as there might still simply be a website with an ad which has an animation which makes it think that you are not afk.
  • Comparing two pictures pixel by pixel is surprisingly slow nowadays due to how high resolution screens are nowadays. We don't want activitywatch to slow down peoples computers significantly (not by default at least, opt in could be an option)

@douglasg14b
Copy link
Author

douglasg14b commented Aug 21, 2019

@johan-bjareholt

I think that actually might be a viable solution, the issues you mentioned are solvable. I wouldn't take it off the table just yet. It's also very simple, and doesn't have a lot of complexities compared to monitoring audio.

You can probably even use something like OpenCV for this, which has a lot of utilities that make this even simpler like absdiff.

Down-scale the image, which actually does two things

  • Reduces the pixel count, say to 50k pixels
  • Removes and reduces small and inconsequential changes (Like a system clock).

Perform cheap math to check the delta from one image to another

  • Sum the squared differences of the pixel values
    • You could also diff the lightness values, which might be even cheaper
  • Set a threshold to avoid false positives. If someone is watch a video, a LOT should be changing. So having a high threshold should be fine.

Don't monitor in real time.

  • Periodic image processing is damn cheap compared to anything in real time, and causes much less of a performance concern. It's trivial to do the above at 30fps even on old hardware. If you are checking for changes, say every 15 seconds, that's 0.2% of the processing power needed.
  • If the concern is performance spikes, then draw out the processing time. Instead of running the loop all in one go, let is sleep for a few microseconds every few loops. IDK how to do this in Python, but in C# it's fairly easy to avoid hogging system resources through asynchronous processing with artificial delays. I imagine Python can d the same.
    • It's also fairly trivial, in my experience, to customize the delays based on prior performance of the device.

Also refer to https://stackoverflow.com/questions/4196453/simple-and-fast-method-to-compare-images-for-similarity

@johan-bjareholt
Copy link
Member

johan-bjareholt commented Aug 21, 2019

@douglasg14b I'd gladly help getting it to work with aw-server and the web-ui if you want to make such an watcher for ActivityWatch, we love to help anyone who wants to collect more data to activitywatch and make it possible for them to analyze it. You can even write the watcher in C# if that's the language you prefer, we have one watcher already which is written in that which you can get some inspiration from (https://github.com/LaggAt/ActivityWatchVS)

However I don't think this is something we want to ship with activitywatch by default and definitely not have turned on by default because:

  • It is not 100% accurate as we have discussed before, you can easily leave your computer with a movie running
  • OpenCV s a pretty big dependency, 26MB for the python bindings
  • Taking a screenshot is not universal on mac, windows and linux and needs to be implemented differently on each platform. Not impossible to do, just needs quite a bit of development and testing. Would probably also need more dependencies
  • I don't think performance will matter a great deal if written efficiently, but I don't believe that it ever will be negligible. If we have to go as far as to have to add artificial delays to avoid taking up a lot of CPU time I personally wouldn't use such an watcher on my laptops at least, probably not on my desktop either.

I personally don't want to spend time on this because I find there to be more important things to fix currently.

@jtrakk
Copy link

jtrakk commented Aug 22, 2019

This seems doable with Pillow and pyscreenshot. Something like this might work, perhaps as a third-party watcher package.

import time

import pyscreenshot
import requests

BUCKET_URL = "http://localhost:5600/api/0/buckets/screenshot-rgb"
INTERVAL = 10

requests.post(BUCKET_URL)

while True:
    # Take a screenshot.
    im = pyscreenshot.grab(childprocess=False)
    # Get average value for each RGB channel.
    rgb = im.resize((1, 1)).getpixel((0, 0))
    # Post the rgb values.
    requests.post(BUCKET_URL + "/heartbeat", json={"rgb": list(rgb)})
    # Wait a few seconds before repeating.
    time.sleep(INTERVAL)

@johan-bjareholt
Copy link
Member

@jtrakk Nice start, a few suggestions:

  • We probably should do the image analysis in the client rather than the server, then simply push the data {"afk": true/false}
  • Instead of doing a proper resize, only picking every fourth pixel or so and doing an average of those should make this faster and still be accurate enough
  • If you want you can use the aw-client python library instead of requests, easier to get things going and has some heartbeat optimizations

@rapiz1
Copy link

rapiz1 commented Jan 21, 2020

I would like to mention the power management tool powerdevil from KDE can detect whether there is a video playing. But I don't know how they achieve that.

@nicolae-stroncea
Copy link
Member

nicolae-stroncea commented Jun 10, 2020

What about doing it inside of the extensions?

Proposal:

  1. Read the DOM of a webpage, query for video elements.
    let listVideo = document.querySelectorAll('video')
  2. In the current tab, check if video is playing by doing:
    if(!listVideo.paused){ videoPlaying = True }
  3. Store 'activeVideo' property in datastr to true.
  4. When checking afk status, if property exists and is true, consider user non-afk

Advantages:

  • Instantly crossplatform, don't need to worry about Mac/Linux/Windows
  • Should be a lot lighter on resources and storage than doing analysis on the screen. Essentially this would be querying a small list of elements(even youtube frontpage has only 1 video element in DOM, though not sure why), and checking a property of it
  • Should be easier to implement

Disadvantages:

  • Would only detect web-based content.
  • Automatically considers video playing as user being active. We're already considering doing this with all of the other approaches, so this one isn't really a disadvantage

I believe that since majority of media is consumed online, the advantages outweigh the disadvantages.

EDIT: further problem

EDIT 2:

A potential problem here is this would not detect content in iframes. A comparatively small (but existent) amount of media is done through iframes. Example is reuters (go watch an article, and it should pop an iframe with an embedded video). Another example is embedding youtube videos, which is also done through iframes. Maybe there are workarounds for this. The only one I found so far is checking for the 'autoplay` property, which if set, indicates video content. However, this is not foolproof.

On further analysis seems like the 'audible' property is indeed the better choice and given that it is the active tab and audio is playing it should indicate the user is watching some content

@nicolae-stroncea
Copy link
Member

I'm currently going through my AW database reviewing all events tagged as audible: true, and overall, all video content is tagged correctly:

  • Normal videos, streaming content, audio/video online calls, etc

There are a couple of false positives:

  • Music Websites
  • Background noise Websites
  • Podcast Websites
  • Radio Websites
  • Outlook(weird outlier. I think it might be notifications that triggered it?)

Since they are purely audio, it is very likely(more often than not, I would say) that a user puts on some music/podcast/radio etc, and then works outside of their computer: typing notes, cleaning, etc. So I think we could have a whitelist of these websites where we consider content as 'afk' even if they have their audio property set to true.

@nicolae-stroncea
Copy link
Member

Found a way to do this directly with Sound Drivers using a Python library called SoundCard. This works with any type of applications, not just web browsers.

Windows and Linux

Tested successfully on both Linux (relies on PulseAudio, so should work on all distributions by default) and Windows (relies on WASAPI, works on Windows 7+).

#!/usr/bin/env python
import soundcard as sc
import numpy as np

'''Get a microphone from a speaker, not the actual microphone'''
def getMic():
    mic = None
    mics = sc.all_microphones(include_loopback=True)
    for a_mic in mics:
        if(a_mic.isloopback):
            mic = a_mic
            break
    return mic

def checkAudio(mic):
    isAudio = False
    if(mic is not None):
        # record 1 second
        data = mic.record(samplerate=48000, numframes=48000)
        isAudio = np.any(data != 0)

    return (isAudio)


mic = getMic()
checkAudio(mic)

Mac

This will not work by default on MacOS because it does not provide loopback functionality.

  1. Mac Users would have to download SoundFlower (also OSS), and set it up so it acts as a 'virtual speaker'.
  2. We need to find name of the speaker. It should always stay the same, so somebody just needs to download it on a Mac and check.
  3. Need a specific check in getMic for MacOS, and then get the mic that has SoundFlower's name.
  4. Rest should be the same

I don't have a Mac to test this, so somebody should confirm to see if this works.

@jmealo
Copy link

jmealo commented Jun 13, 2020

@nicolae-stroncea I'll try this on my Macbook and report back shortly.

@nicolae-stroncea
Copy link
Member

@jmealo Not sure if you already found it, but this tutorial seemed useful to me. It helps avoid some potential pitfalls of the setup, specifically that if you don't set multi-output, your Mac won't play any sound at all since all of it will be routed only to SoundFlower. It also explains how to select SoundFlower as an input device, which is what we need

@jmealo
Copy link

jmealo commented Jun 13, 2020

I'll still test Soundflower, but, I found this:
https://stackoverflow.com/questions/27604207/applescript-check-if-computer-is-playing-any-sound#27608712

When I play a YouTube video in Chrome:

pmset -g | grep coreaudiod
sleep                1 (sleep prevented by sharingd, Google Chrome, coreaudiod, useractivityd)

When I paused the video coreaudiod stopped preventing sleep and no longer appeared in the output.

I fired up Zoom, with no meeting there was no output, upon starting a new meeting:

 hibernatefile        /var/vm/sleepimage
 disksleep            0
 sleep                1 (sleep prevented by sharingd, coreaudiod, coreaudiod)
 displaysleep         2 (display sleep prevented by zoom.us)

As far as false positives go: assuming a browser extension, you can differentiate between listening to music/watching a video.

If you poll this at regular intervals, you don't have to worry about notifications much. It seems like video conferencing will prevent the display from sleeping. I can test with something that uses WebRTC and verify.

@jmealo
Copy link

jmealo commented Jun 13, 2020

I'm providing the output of some pmset commands that should provide information helpful for time/activity tracking:

While playing a Youtube video in Chrome:

2020-06-13 14:16:26 -0400 
Assertion status system-wide:
   BackgroundTask                 0
   ApplePushServiceTask           0
   UserIsActive                   1
   PreventUserIdleDisplaySleep    0
   PreventSystemSleep             0
   ExternalMedia                  0
   PreventUserIdleSystemSleep     1
   NetworkClientActive            0
Listed by owning process:
   pid 434(sharingd): [0x0000377400018c33] 00:00:40 PreventUserIdleSystemSleep named: "Handoff"  
   pid 626(Google Chrome): [0x000036c100018c27] 00:03:39 NoIdleSleepAssertion named: "Playing audio"  
   pid 273(mds_stores): [0x0000379c000b8c46] 00:00:00 BackgroundTask named: "com.apple.metadata.mds_stores.power"  
   pid 198(coreaudiod): [0x0000366f000180cb] 00:05:01 PreventUserIdleSystemSleep named: "com.apple.audio.AppleHDAEngineOutput:1B,0,1,1:0.context.preventuseridlesleep"  
	Created for PID: 742. 
   pid 431(useractivityd): [0x0000379a00018c45] 00:00:01 PreventUserIdleSystemSleep named: "BTLEAdvertisement"  
	Timeout will fire in 58 secs Action=TimeoutActionTurnOff
   pid 151(hidd): [0x0000365400098c0a] 00:00:00 UserIsActive named: "com.apple.iohideventsystem.queue.tickle serviceID:100000363 name:AppleEmbeddedKeyboa product:Apple Internal Keyb eventType:3"  
	Timeout will fire in 120 secs Action=TimeoutActionRelease
No kernel assertions.
Idle sleep preventers: IODisplayWrangler

While in a Zoom meeting (it looks like the developers forgot to provide the correct value for the activity):

% pmset -g assertions
2020-06-13 14:18:49 -0400 
Assertion status system-wide:
   BackgroundTask                 0
   ApplePushServiceTask           0
   UserIsActive                   1
   PreventUserIdleDisplaySleep    1
   PreventSystemSleep             0
   ExternalMedia                  0
   InternalPreventDisplaySleep    1
   PreventUserIdleSystemSleep     1
   NetworkClientActive            0
Listed by owning process:
   pid 26724(zoom.us): [0x0000381e00058c76] 00:00:12 NoDisplaySleepAssertion named: "Describe Activity Type"  
   pid 434(sharingd): [0x0000377400018c33] 00:03:02 PreventUserIdleSystemSleep named: "Handoff"  
   pid 106(powerd): [0x0000381600108002] 00:00:20 InternalPreventDisplaySleep named: "com.apple.powermanagement.delayDisplayOff"  
	Timeout will fire in 100 secs Action=TimeoutActionTurnOff
   pid 431(useractivityd): [0x0000382700018c78] 00:00:03 PreventUserIdleSystemSleep named: "BTLEAdvertisement"  
	Timeout will fire in 56 secs Action=TimeoutActionTurnOff
   pid 384(nsurlsessiond): [0x0000382800018c7a] 00:00:02 PreventUserIdleSystemSleep named: "NSURLSessionTask ADC0E368-B668-4A09-B48C-B1B11C78F152"  
	Timeout will fire in 10798 secs Action=TimeoutActionTurnOff
   pid 384(nsurlsessiond): [0x0000382800018c7b] 00:00:02 PreventUserIdleSystemSleep named: "NSURLSessionTask B2ED8888-9B0E-4A54-9F6F-207CFA4B82A2"  
	Timeout will fire in 10798 secs Action=TimeoutActionTurnOff
   pid 198(coreaudiod): [0x0000381f00018c5c] 00:00:11 PreventUserIdleSystemSleep named: "com.apple.audio.AppleHDAEngineOutput:1B,0,1,1:0.context.preventuseridlesleep"  
	Created for PID: 26724. 
   pid 198(coreaudiod): [0x0000381e00018c58] 00:00:12 PreventUserIdleSystemSleep named: "com.apple.audio.AppleHDAEngineInput:1B,0,1,0:1.context.preventuseridlesleep"  
	Created for PID: 26724. 
   pid 151(hidd): [0x0000365400098c0a] 00:00:00 UserIsActive named: "com.apple.iohideventsystem.queue.tickle serviceID:100000363 name:AppleEmbeddedKeyboa product:Apple Internal Keyb eventType:3"  
	Timeout will fire in 120 secs Action=TimeoutActionRelease
No kernel assertions.
Idle sleep preventers: IODisplayWrangler

@nicolae-stroncea
Copy link
Member

Also found this command, which seems to draw inspiration from same source :
if [[ "$(pmset -g | grep ' sleep')" == *"coreaudiod"* ]]; then echo audio is playing; else echo no audio playing; fi

It doesn't have the same level of detail, but can give a quick, cheap check if audio is playing

@jmealo
Copy link

jmealo commented Jun 13, 2020

@nicolae-stroncea: you can do all sorts of activity tracking beyond what you set out to do on OSX with pmset -g assertions, you can see whether the user clicks, scrolls, touches, multi-touches, types, etc... (it logs whatever resets the idle user timeout, as well as a count down, you can infer a great deal from this). Additionally, we get verbose logging of what's keeping the system from sleeping, which includes playing audio/video or using the webcam.

I wasn't able to get your Python to run, is it Python 2? I think it's a dead-end (but good idea! especially without having access to the hardware) given what I'm able to do by tailing the power telemetry from OSX.

Using pmset is low-overhead, can run as an unprivileged user, and doesn't require a third-party kernel extension, so it seems like the right way to approach what you set out to do (and then some!). It honestly seems like a bit of an oversight from a privacy perspective shrug.

@nicolae-stroncea
Copy link
Member

@jmealo that's pretty neat! I imagine there's a lot of nice aw-watcher possibilities lying in there.

The script is Python3, but it would need some customizing for Mac to get it working with soundcard:

  1. Once you install SoundFlower, you would need to query all of the microphones, and find the name that MacOS uses for SoundFlower: sc.all_microphones(). Iterate through them, then get the name of each microphone by doing the_mic.name, to find what name SoundFlower goes by.
  2. Once you get the name by looking through the mics, you can get the microphone by the name: mic = sc.get_speaker('name_of_soundflower_input')

I agree that since pmset ... is lower overhead, it would be preferred.


I looked for a similar command that could be useful on Linux, and found: pacmd list-sink-inputs (again dependant on the pulseaudio, and I don't think there is a lot of fragmentation on this front). You can find if any sound is running by doing: pacmd list-sink-inputs | grep -w state | grep RUNNING. A pacmd list-sink-inputs returns info on the application running which is useful:

    index: 173
	driver: <protocol-native.c>
	flags: START_CORKED 
	state: RUNNING
	sink: 1 <alsa_output.pci-0000_00_1f.3.analog-stereo>
	volume: front-left: 52016 /  79% / -6.02 dB,   front-right: 52016 /  79% / -6.02 dB
	        balance 0.00
	muted: no
	current latency: 89.25 ms
	requested latency: 75.01 ms
	sample spec: float32le 2ch 44100Hz
	channel map: front-left,front-right
	             Stereo
	resample method: copy
	module: 10
	client: 17 <Firefox>
	properties:
		media.name = "AudioStream"
		application.name = "Firefox"
		native-protocol.peer = "UNIX socket client"
		native-protocol.version = "33"
		application.process.id = "5675"
		application.process.user = "nicolae"
		application.process.host = "nicolae"
		application.process.binary = "firefox"
		application.language = "en_US.UTF-8"
		window.x11.display = ":0"
		application.icon_name = "firefox"
		module-stream-restore.id = "sink-input-by-application-name:Firefox"

There are a couple of weird quirks that I didn't figure out about this. If I mute an application (but allow it to run), it will still show up with state: RUNNING and muted: no so not sure why this happens.

Wasn't able to find any similar command for Windows that we would be able to trigger directly from Python, but I'm not too familiar with developing on the platform. Worst case, soundcard could still be used for the cases where a reliable low-overhead platform-dependent command is not found.

@jmealo
Copy link

jmealo commented Jun 13, 2020

@jmealo that's pretty neat! I imagine there's a lot of nice aw-watcher possibilities lying in there.

The script is Python3, but it would need some customizing for Mac to get it working with soundcard:

  1. Once you install SoundFlower, you would need to query all of the microphones, and find the name that MacOS uses for SoundFlower: sc.all_microphones(). Iterate through them, then get the name of each microphone by doing the_mic.name, to find what name SoundFlower goes by.
  2. Once you get the name by looking through the mics, you can get the microphone by the name: mic = sc.get_speaker('name_of_soundflower_input')

For what it's worth: Soundflower (2ch) or Soundflower (64ch) seem to be the device names.

I agree that since pmset ... is lower overhead, it would be preferred.

I looked for a similar command that could be useful on Linux, and found: pacmd list-sink-inputs (again dependant on the pulseaudio, and I don't think there is a lot of fragmentation on this front). You can find if any sound is running by doing: pacmd list-sink-inputs | grep -w state | grep RUNNING. A pacmd list-sink-inputs returns info on the application running which is useful:

    index: 173
	driver: <protocol-native.c>
	flags: START_CORKED 
	state: RUNNING
	sink: 1 <alsa_output.pci-0000_00_1f.3.analog-stereo>
	volume: front-left: 52016 /  79% / -6.02 dB,   front-right: 52016 /  79% / -6.02 dB
	        balance 0.00
	muted: no
	current latency: 89.25 ms
	requested latency: 75.01 ms
	sample spec: float32le 2ch 44100Hz
	channel map: front-left,front-right
	             Stereo
	resample method: copy
	module: 10
	client: 17 <Firefox>
	properties:
		media.name = "AudioStream"
		application.name = "Firefox"
		native-protocol.peer = "UNIX socket client"
		native-protocol.version = "33"
		application.process.id = "5675"
		application.process.user = "nicolae"
		application.process.host = "nicolae"
		application.process.binary = "firefox"
		application.language = "en_US.UTF-8"
		window.x11.display = ":0"
		application.icon_name = "firefox"
		module-stream-restore.id = "sink-input-by-application-name:Firefox"

There are a couple of weird quirks that I didn't figure out about this. If I mute an application (but allow it to run), it will still show up with state: RUNNING and muted: no so not sure why this happens.

What a great find! I was looking to see if systemd had something, but, if pulseaudio can be queried directly that'd be good. I can't imagine that there's not a similar solution on any *nix based OS.

Wasn't able to find any similar command for Windows that we would be able to trigger directly from Python, but I'm not too familiar with developing on the platform. Worst case, soundcard could still be used for the cases where a reliable low-overhead platform-dependent command is not found.

It looks like powercfg is what we're looking for on Windows. I'm hoping that read-only operations don't require elevated permissions, write ones certainly do.

@jmealo
Copy link

jmealo commented Jun 13, 2020

It looks like the output of powercfg -requests looks something like this (found on Microsoft answers for troubleshooting sleep issues):

SYSTEM:
[DRIVER] Cirrus Logic High Definition Audio (HDAUDIO\FUNC_01&VEN_ ...)
An audio stream is currently in use.
[PROCESS] \Device\HarddiskVolume2\Program Files (x86)\Windows Media Player\wmplayer.exe

Here's the documentation that I found for the command so far:
https://docs.microsoft.com/en-us/windows-hardware/design/device-experiences/powercfg-command-line-options

@jmealo
Copy link

jmealo commented Jun 13, 2020

I just tested on Windows:

No video/audio playing:

Microsoft Windows [Version 10.0.18363.900]
(c) 2019 Microsoft Corporation. All rights reserved.

C:\Windows\system32>powercfg -requests
DISPLAY:
None.

SYSTEM:
None.

AWAYMODE:
None.

EXECUTION:
None.

PERFBOOST:
[DRIVER] Legacy Kernel Caller
Power Manager

ACTIVELOCKSCREEN:
None.

Video playing:

C:\Windows\system32>powercfg -requests
DISPLAY:
[PROCESS] \Device\HarddiskVolume7\Program Files (x86)\Google\Chrome\Application\chrome.exe
Video Wake Lock

SYSTEM:
[DRIVER] NVIDIA High Definition Audio (HDAUDIO\FUNC_01&VEN_10DE&DEV_0072&SUBSYS_38423967&REV_1001\5&34bd84db&0&0001)
An audio stream is currently in use.

AWAYMODE:
None.

EXECUTION:
[PROCESS] \Device\HarddiskVolume7\Program Files (x86)\Google\Chrome\Application\chrome.exe
Playing audio

PERFBOOST:
None.

ACTIVELOCKSCREEN:
None.

Audio playing:

C:\Windows\system32>powercfg -requests
DISPLAY:
None.

SYSTEM:
[DRIVER] NVIDIA High Definition Audio (HDAUDIO\FUNC_01&VEN_10DE&DEV_0072&SUBSYS_38423967&REV_1001\5&34bd84db&0&0001)
An audio stream is currently in use.

AWAYMODE:
None.

EXECUTION:
[PROCESS] \Device\HarddiskVolume7\Program Files (x86)\Google\Chrome\Application\chrome.exe
Playing audio

PERFBOOST:
None.

ACTIVELOCKSCREEN:
None.

@nicolae-stroncea
Copy link
Member

@jmealo nice find! I just tested it, and it works well. Unfortunately, it requires administrative privileges. I had to run powershell as an administrator to get it to work.

Here's the output I got when playing a youtube video for it:

DISPLAY:
[PROCESS] \Device\HarddiskVolume3\Program Files\Mozilla Firefox\firefox.exe
[PROCESS] \Device\HarddiskVolume3\Program Files\Mozilla Firefox\firefox.exe
[PROCESS] \Device\HarddiskVolume3\Program Files\Mozilla Firefox\firefox.exe
[PROCESS] \Device\HarddiskVolume3\Program Files\Mozilla Firefox\firefox.exe
[PROCESS] \Device\HarddiskVolume3\Program Files\Mozilla Firefox\firefox.exe
[PROCESS] \Device\HarddiskVolume3\Program Files\Mozilla Firefox\firefox.exe
[PROCESS] \Device\HarddiskVolume3\Program Files\Mozilla Firefox\firefox.exe
[PROCESS] \Device\HarddiskVolume3\Program Files\Mozilla Firefox\firefox.exe
[PROCESS] \Device\HarddiskVolume3\Program Files\Mozilla Firefox\firefox.exe
[PROCESS] \Device\HarddiskVolume3\Program Files\Mozilla Firefox\firefox.exe
[PROCESS] \Device\HarddiskVolume3\Program Files\Mozilla Firefox\firefox.exe

SYSTEM:
[DRIVER] Realtek Audio (INTELAUDIO\FUNC_01&VEN_10EC&DEV_0298&SUBSYS_1028087C&REV_1001\4&2223f159&2&0001)
An audio stream is currently in use.

AWAYMODE:
None.

EXECUTION:
None.

PERFBOOST:
None.

ACTIVELOCKSCREEN:
None.

@tmladek
Copy link

tmladek commented Feb 22, 2021

I'm sure I'm not the first to come up with this, but what about doing away with complex heurestics and just having a switch "Consider time spent in fullscreen applications as not-afk"?

  • When I'm watching video, I'm most likely going to be doing so in fullscreen, and I'd like to count this as not AFK.
    • I can't really think of a single instance of turning on a fullscreen application and then walking away from my computer - and if that happens, I think I would be happy with errorneously registering this as not AFK.
  • If I'm not watching video in fullscreen, then it's probably because I'm doing something else at the same time, hence AFK watcher will work as expected.

@ErikBjare
Copy link
Member

ErikBjare commented Feb 22, 2021 via email

@tmladek
Copy link

tmladek commented Feb 22, 2021

Yup, but it's a simple one, and as I've said above, I can't really think of an instance where it would fail for me (or at least, fail worse than what we've got now), and if it does, I think I would be fine with the result.

you watch a movie/play a game, but pause/leave the device for a while to take a break

That would result in a couple minutes of wrongly tagged time, as opposed to literal hours.

Additionally, pausing an online game does vaguely match what I would consider "not-AFK" time - a fullscreen app running directly implies that attention is meant to be paid to the computer. I understand this may be a point of contention, but that's why I'm appealing to the simplicity of the heurestic. It's easy to read, since it doesn't rely on complex OS machinery relating to audio, or combinations of window titles and somewhat arbitrary (1hr30) idleness thresholds, etc.

If there's an artifact, it's likely going to be small(er) and easily recognized. And if it's an issue, well, it's a single switch after all. It can even by false by default.

I'm not even sure if it's feasible to detect if an application is fullscreen in a reliable and cross-platform manner.

Well, this thread seems to be considering metrics which so far do not seem feasible to detect even on any single operating system! :) So I didn't think this would be that much of a hurdle.

The bottom line is: This is a somewhat major issue for an activity tracking software, and has been unsolved for more than 2 years. I do appreciate its difficulty and complexity, and I don't claim that fullscreen-tracking is the final solution, but! I think it's a feasible partial solution, and if it were present, I would have simply toggled it on, and went on with my day. "Don't let perfect be the enemy of the good", yadda yadda.

@ErikBjare
Copy link
Member

ErikBjare commented Feb 22, 2021

but it's a simple one

I don't think it's any simpler than most of the other things already suggested (but it's a good addition still!).

That would result in a couple minutes of wrongly tagged time, as opposed to literal hours.

Everyone's usage is different. I often leave my computer with a game running for hours. I also sometimes fall asleep to a video playing. Personally, I'm not inclined to implement & maintain a feature I won't have any use of myself.

Well, this thread seems to be considering metrics which so far do not seem feasible to detect even on any single operating system! :) So I didn't think this would be that much of a hurdle.

It does! But those hurdles are exactly why it hasn't been implemented. Although I disagree that the other proposed solutions aren't feasible to detect on a single OS, and I think it's about as feasible to check if audio is playing vs if a window is fullscreen (but hard to speculate without seeing example code for the latter).

The bottom line is: This is a somewhat major issue for an activity tracking software, and has been unsolved for more than 2 years. I do appreciate its difficulty and complexity, and I don't claim that fullscreen-tracking is the final solution, but! I think it's a feasible partial solution, and if it were present, I would have simply toggled it on, and went on with my day. "Don't let perfect be the enemy of the good", yadda yadda.

100%. However, the 'good' solution that's the most likely to get implemented anytime soon is using the audible attribute reported by aw-watcher-web (ActivityWatch/aw-webui#85). It will only work when you use your browser for watching videos (which happens to be the case for me most of the time) and is obviously not perfect, but "Don't let perfect be the enemy of the good", yadda yadda ;)

@ErikBjare
Copy link
Member

I just merged ActivityWatch/aw-webui#262 which implements the "audible-as-active" feature. It makes it so that if your browser is the active app, and the active browser tab is audible (playing sound), then it will not count that time as AFK (and therefore make it show on your Activity view).

It requires that you're running the web watcher for your browser.

This vastly improves the situation when you watch a video in your web browser, but it is not a complete solution, so I'll leave the issue open for now.

@archiif
Copy link

archiif commented Mar 12, 2021

Will this be available in the nightly build soon? It seems that the aw-webui module is still pinned to the commit from last December.

@johan-bjareholt
Copy link
Member

@archiif I just updated aw-server and aw-webui in the main activitywatch repo, should hopefully work.
The recent aw-webui change however does not work on aw-server-rust yet though.

@johan-bjareholt
Copy link
Member

@archiif I have fixed the integrations tests now too so the nightly builds are now working again.

@luckydonald
Copy link

luckydonald commented Apr 7, 2021

Is that optional? I use the Chrome plugin, and I have music (youtube video though) running pretty much all the time no matter if I'm in front of it or not.

@johan-bjareholt
Copy link
Member

@luckydonald Yes it's optional, you can turn it off in the settings.

@k8ieone
Copy link

k8ieone commented Jun 17, 2021

Lots of programs use MPRIS for sending playback info to the system (at least on Linux, I'm not sure about other platforms) so that it can be shown in various places in the system. Just throwing it out there as a possible way to cheaply and universally check if media is playing.
Example from GNOME (media playing in Firefox):
image

@ahmednofal
Copy link

ahmednofal commented Jul 7, 2021

I just merged ActivityWatch/aw-webui#262 which implements the "audible-as-active" feature. It makes it so that if your browser is the active app, and the active browser tab is audible (playing sound), then it will not count that time as AFK (and therefore make it show on your Activity view).

It requires that you're running the web watcher for your browser.

This vastly improves the situation when you watch a video in your web browser, but it is not a complete solution, so I'll leave the issue open for now.

what about zoom or conference call apps. I am afraid I find zoom being AFKed most of the time. And it counts actually towards my productivity hours 😞

@corradio
Copy link

corradio commented Oct 22, 2021

what about zoom or conference call apps. I am afraid I find zoom being AFKed most of the time. And it counts actually towards my productivity hours 😞

I have the same issue - very happy to contribute if someone can help shape the solution. What about having an exclusion regexp for the afk watcher?

@marco-coraggio
Copy link

Just wanted to say I had the same issue with conferencing apps like Zoom, Teams or Skype. Is there any solution to that? Thank you.

@ErikBjare
Copy link
Member

ErikBjare commented Sep 28, 2022

I've been thinking of adding a "always count as active" regex configuration rule (possibly with a max-duration, such that actually long AFK periods don't get included by accident).

This would be easy to implement and work as a decent workaround, I think.

Would also help in some situations I've noticed where gamepads aren't detected.

Edit: basically what was suggested by @placeybordeaux #261 (comment)

@marco-coraggio
Copy link

That would be great, I would be very grateful if you could do that!! Thank you in any case.

@8Dion8
Copy link

8Dion8 commented Oct 25, 2022

I've been thinking of adding a "always count as active" regex configuration rule (possibly with a max-duration, such that actually long AFK periods don't get included by accident).

I feel like that would be a great temporary solution for now. Tried messing around with manual SQL queries to edit existing afk data, and got nowhere. If there was a switch for certain applications, not even in the trackers but in the UI (which would probably be evevn easier to implement and doesn't actually modify data for now), then it would be great

@Aziks0
Copy link

Aziks0 commented Oct 25, 2022

@8Dion8 It has been implemented, see ActivityWatch/aw-webui#375.

Just need to wait for the next release now 👀.

@8Dion8
Copy link

8Dion8 commented Oct 26, 2022

Awesome! Can't wait to see how much time I actually wasted on binging shows😂

@Valentin-N
Copy link

I'm testing the new feature in ActivityWatch/aw-webui#375 which landed in v0.12.2b1 and I am seeing failures that cause the whole Activity page to stop loading.

2023-01-18 22:15:41 [ERROR]: Exception on /api/0/query/ [POST]  (aw-server:1449)
Traceback (most recent call last):
  File "flask/app.py", line 1517, in full_dispatch_request
  File "flask/app.py", line 1503, in dispatch_request
  File "aw_server/rest.py", line 46, in decorator
  File "flask_restx/api.py", line 405, in wrapper
  File "flask/views.py", line 84, in view
  File "flask_restx/resource.py", line 46, in dispatch_request
  File "aw_server/rest.py", line 323, in post
  File "aw_server/api.py", line 310, in query2
  File "aw_query/query2.py", line 417, in query
  File "aw_query/query2.py", line 391, in interpret
  File "aw_query/query2.py", line 140, in interpret
  File "aw_query/functions.py", line 88, in g
  File "aw_query/functions.py", line 117, in g
  File "aw_query/functions.py", line 198, in q2_filter_keyvals_regex
  File "aw_transform/filter_keyvals.py", line 28, in filter_keyvals_regex
  File "aw_transform/filter_keyvals.py", line 28, in <listcomp>
  File "aw_transform/filter_keyvals.py", line 26, in predicate
KeyError: 'title'

@cjc7373
Copy link

cjc7373 commented Nov 15, 2023

Just a few notes about mpv under Linux wayland environment. Its manual says:

Disabling Screensaver
By default, mpv tries to disable the OS screensaver during playback (only if a VO using the OS GUI API is active). --stop-screensaver=no disables this.

A common problem is that Linux desktop environments ignore the standard screensaver APIs on which mpv relies. In particular, mpv uses the Screen Saver extension (XSS) on X11, and the idle-inhibit protocol on Wayland.

Before mpv 0.33.0, the X11 backend ran xdg-screensaver reset in 10 second intervals when not paused in order to support screensaver inhibition in these environments. This functionality was removed in 0.33.0, but it is possible to call the xdg-screensaver command line program from a user script instead.

So maybe we can utilize this "idle-inhibit protocol". And this protocol is tied to the idle notify protocol, which says:

If an idle inhibitor is active (e.g. another client has created a zwp_idle_inhibitor_v1 on a visible surface), the compositor must not make the notification object idle.

So if we implement idle notify in the afk watcher, it just works! Nothing need to be handled.

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

Successfully merging a pull request may close this issue.