Profile picture is bugging

March 3rd Somtime the picture of my profile is bugging i think it doesn’t load right or something. Screenshot_1

2 Likes

Same here :raising_hand_man:

2 Likes

Hello I already made a Topic but nothing has changed so here I am again. Every time I’m tabbing out my Profile picture is bugging and when I tab back it is normal. I don’t know why hoping that this would be fixed soonScreenshot_1

No need for a second thread then.
We know about the issue but this is currently not on highest priority.

4 Likes

Hey there,
When I tap out of TeamSpeak 5 to my desktop or another program the profile picture goes 75% dark
I don’t know how that happens or why that happens, but I really would like to have that fixed. To me, it seems like a bug, but I don’t know. Maybe it is fixable somehow?
Screenshot_244
Screenshot_245

1 Like

This is chrome doing it and we hope that the next version will fix this.
But till the version was released as a stable version we have to live with that rending bug.

We guess it will take 3-5 weeks till the update for this is public.

5 Likes

I am glad that you know what the problem source is. Thanks for the response, keep up the good work <3

:christmas_tree: Merry Christmas, TeamSpeak. I got a present for you! :snowflake:


I ran some tests on the First Frame Extractor (FFE) to identify some of the underlying problems causing the occasional erroneous extractions. In this post, I will run through my ideas, tests, and conclusions.

What even is the problem?

When the blur event is emitted from the frontend, all image sources within the client, like avatars and file transfers, are changed to contain the ts-frame=1 option. This is used to turn off animations when the client is not focused. In the background, this option will trigger the FFE to generate a simple static image. For non-moving images, this should not make any difference. It should return the first frame for animated images, like APNGs or GIFs. However, the FFE sometimes returns a broken image, usually as a prematurely truncated stream. This manifests in the image having the correct size, as the shape is part of the file header but is transparent, starting from a random pixel and going downwards.

expected cat
broken cat

On the left is an expected image, and on the right is the same, but broken one.

Where does this happen?

At this point, it is unclear where exactly this error occurs in the pipeline. The data returned from the GET request is already compromised when the error occurs, thus excluding the render pipeline of the chromium-embedded framework (CEF). As I cannot access the source code and running the released client in a debugger is not feasible, I can only run tests on the entire pipeline between web request and response. This makes any conclusion on the origin of the bug pure speculation.

How does this happen?

My first assumption was congestion in either the handler for the custom tsic protocol used within the client or the FFE itself. This can be tested by measuring the percentage of incorrect results returned from the FFE, given a changing interval between requests. It is to be noted that in this test, only image byte size is compared, thus potentially excluding other errors. The avatar image used for the test has an expected size of 96947 Bytes. The following code was used for this test:

let tries = 100;
for (timeout in [0, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000]) {
    for (let k = 0; k < 5; k++) {
        let failed = 0;
        for (let i = 0; i < tries; i++) {
            fetch("tsic://avatar/<redacted>?ts-frame=1", {
                cache: "no-store",
                mode: "no-cors"
            }).then(resp => resp.arrayBuffer()).then((i) => i.byteLength != 96947 && failed++);
            await new Promise((res, rej) => setTimeout(res, timeout));
        }
        console.error(`${failed}/${tries} failed with timeout ${timeout}`);
    }
}

Executing this yields the following results.
image

The y axis shows the fails per 100 requests. The different boxes show the results per interval. It is evident that there is no significant difference between the various intervals. This suggests that the fault is not caused by congestion. As such, all following tests will be performed without a timeout. Overall, an error rate of 2.82 % can be observed.

My next idea was to compare the performance of the FFE, given different images. This is also a relatively simple test after bringing the client into a state with a few images shown. For this test, the following code was used:

let tries = 1000;
let images = [].slice.call(document.getElementsByTagName("IMG")).map((image) => image.src);
images = new Set(images.filter((image) => image.endsWith("ts-frame=1") && image.startsWith("tsic://")));
Promise.all(images.values().map(async (image) => {
    let expected_size = await fetch(image, {
        cache: "no-store",
        mode: "no-cors"
    }).then((r) => r.arrayBuffer()).then((i) => i.byteLength);
    let output = [];
    for (let k = 0; k < 5; k++) {
        let failed = 0;
        for (let j = 0; j < tries; j++) {
            await fetch(image, {
                cache: "no-store",
                mode: "no-cors"
            }).then((r) => r.arrayBuffer()).then((i) => i.byteLength != expected_size && failed++);
        }
        output.push(`${failed}/${tries} failed with image ${image}`);
    }
    return output;
})).then((r) => {
    for (file of r) {
        for (row of file) {
            console.error(row);
        }
    }
})

An interesting observation can be made from the results. Some of the files seem never to fail. Each cell shows the number of failures on 1000 tries.

Avatar 1 Avatar 2 Avatar 3 Avatar 4 Avatar 5 Avatar 6 Avatar 7 Avatar 8 Avatar 9 Avatar 10 Avatar 11 Avatar 12 Avatar 13 Avatar 14 Avatar 15 Avatar 16 Avatar 17
10 0 10 0 0 0 0 14 4 11 2 5 0 4 9 7 8
7 0 10 0 0 0 0 10 2 10 2 2 0 7 11 13 15
14 0 13 0 0 0 0 12 9 13 2 5 0 7 12 13 4
14 0 23 0 0 0 0 24 5 39 4 5 0 6 48 44 23
52 0 143 0 0 0 0 131 40 130 11 15 0 32 215 201 117

I ran the UNIX file command on each image to identify potential differences in these files. However, all images result in more or less the same result, only differing slightly in size. This difference does not correlate to the failures.

PNG image data, 320 x 320, 8-bit/color RGBA, non-interlaced

However, when simply considering the file size, it can be observed that all files that exhibit erroneous behavior are larger than those that don’t:

Avatar  1:  114581 bytes
Avatar  2:   56227 bytes # no failures
Avatar  3:  151264 bytes
Avatar  4:   60282 bytes # no failures
Avatar  5:   52297 bytes # no failures
Avatar  6:   28633 bytes # no failures
Avatar  7:    6868 bytes # no failures
Avatar  8:  141947 bytes
Avatar  9:  103297 bytes
Avatar 10:  192326 bytes
Avatar 11:   82214 bytes
Avatar 12:   85938 bytes
Avatar 13:   54199 bytes # no failures
Avatar 14:   99179 bytes
Avatar 15:  196727 bytes
Avatar 16:  237844 bytes
Avatar 17:  150776 bytes

This suggests the error starts between 60282 and 82214 Bytes, presumably 2^16. This leads me to believe the FFE processes images in buffers of said size. By slightly modifying the code from the previous test, we can check whether the broken images are cut off at a multiple of 2^16 bytes:

let tries = 1000;
let images = [].slice.call(document.getElementsByTagName("IMG")).map((image) => image.src);
images = new Set(images.filter((image) => image.endsWith("ts-frame=1") && image.startsWith("tsic://")));
let failed = 0;
let expected_failure = 0;
Promise.all(images.values().map(async (image) => {
    let expected_size = await fetch(image, {
        cache: "no-store",
        mode: "no-cors"
    }).then((r) => r.arrayBuffer()).then((i) => i.byteLength);
    for (let k = 0; k < 5; k++) {
        for (let j = 0; j < tries; j++) {
            await fetch(image, {
                cache: "no-store",
                mode: "no-cors"
            }).then((r) => r.arrayBuffer()).then((i) => {
                if (i.byteLength != expected_size) {
                    if (i.byteLength % 2**16) {
                        expected_failure++;
                    } else {
                        failed++;
                    }
                }
            });
        }
    }
    return output;
})).then((r) => {
    console.error(`${failed}/${tries*images.size*5} failed unexpectedly`);
    console.error(`${expected_failure}/${tries*images.size*5} failed on multiple of 2^16`); 
})

This presents the following results:

244/145000 failed unexpectedly
899/145000 failed on multiple of 2^16

Given that 78.65% of all broken images are truncated on a multiple of 2^16, I ran further tests on 300 tries over 22 randomly chosen images. The source for this test suite can be found on Github.

πŸ” Analyzing subject: 1
  πŸ“ Expected size: 5284 bytes
  βœ… No broken files
πŸ” Analyzing subject: 2
  πŸ“ Expected size: 6868 bytes
  βœ… No broken files
πŸ” Analyzing subject: 3
  πŸ“ Expected size: 13727 bytes
  βœ… No broken files
πŸ” Analyzing subject: 4
  πŸ“ Expected size: 7031 bytes
  βœ… No broken files
πŸ” Analyzing subject: 5
  πŸ“ Expected size: 12109 bytes
  βœ… No broken files
πŸ” Analyzing subject: 6
  πŸ“ Expected size: 7983 bytes
  βœ… No broken files
πŸ” Analyzing subject: 7
  πŸ“ Expected size: 20491 bytes
  βœ… No broken files
πŸ” Analyzing subject: 8
  πŸ“ Expected size: 20602 bytes
  βœ… No broken files
πŸ” Analyzing subject: 9
  πŸ“ Expected size: 28633 bytes
  βœ… No broken files
πŸ” Analyzing subject: 10
  πŸ“ Expected size: 45525 bytes
  βœ… No broken files
πŸ” Analyzing subject: 11
  πŸ“ Expected size: 42277 bytes
  βœ… No broken files
πŸ” Analyzing subject: 12
  πŸ“ Expected size: 56227 bytes
  βœ… No broken files
πŸ” Analyzing subject: 13
  πŸ“ Expected size: 52297 bytes
  βœ… No broken files
πŸ” Analyzing subject: 14
  πŸ“ Expected size: 107862 bytes
  ⚠️ Found 21.33% broken files (64/300)
  πŸ“ Checking 63 broken files (21.00%) of size 65536
    βœ… All files are the same
    βœ… The files are truncated at a multiple of 2^16
  πŸ“ Checking 1 broken files (0.33%) of size 173398
    βœ… All files are the same
    ❌ The files exhibit an unexpected size
    πŸ“ The files differ in content at offset 65536 from the expectation
    🟰 The unexpected content matches the expectation between offset 65536 and the end
    πŸŸ₯ The files have 65536 duplicate bytes in the middle
πŸ” Analyzing subject: 15
  πŸ“ Expected size: 114581 bytes
  ⚠️ Found 10.67% broken files (32/300)
  πŸ“ Checking 32 broken files (10.67%) of size 65536
    βœ… All files are the same
    βœ… The files are truncated at a multiple of 2^16
πŸ” Analyzing subject: 16
  πŸ“ Expected size: 122237 bytes
  ⚠️ Found 11.33% broken files (34/300)
  πŸ“ Checking 33 broken files (11.00%) of size 65536
    βœ… All files are the same
    βœ… The files are truncated at a multiple of 2^16
  πŸ“ Checking 1 broken files (0.33%) of size 187773
    βœ… All files are the same
    ❌ The files exhibit an unexpected size
    πŸ“ The files differ in content at offset 65536 from the expectation
    🟰 The unexpected content matches the expectation between offset 65536 and the end
    πŸŸ₯ The files have 65536 duplicate bytes in the middle
πŸ” Analyzing subject: 17
  πŸ“ Expected size: 126071 bytes
  ⚠️ Found 6.33% broken files (19/300)
  πŸ“ Checking 17 broken files (5.67%) of size 65536
    βœ… All files are the same
    βœ… The files are truncated at a multiple of 2^16
  πŸ“ Checking 2 broken files (0.67%) of size 191607
    βœ… All files are the same
    ❌ The files exhibit an unexpected size
    πŸ“ The files differ in content at offset 65536 from the expectation
    🟰 The unexpected content matches the expectation between offset 65536 and the end
    πŸŸ₯ The files have 65536 duplicate bytes in the middle
πŸ” Analyzing subject: 18
  πŸ“ Expected size: 150717 bytes
  ⚠️ Found 9.00% broken files (27/300)
  πŸ“ Checking 25 broken files (8.33%) of size 85181
    βœ… All files are the same
    ❌ The files exhibit an unexpected size
    πŸ“ The files differ in content at offset 65536 from the expectation
    🟰 The unexpected content matches the expectation between offset 65535 and the end
    πŸŸ₯ The files are missing 65536 bytes in the middle
  πŸ“ Checking 2 broken files (0.67%) of size 216253
    βœ… All files are the same
    ❌ The files exhibit an unexpected size
    πŸ“ The files differ in content at offset 65536 from the expectation
    🟰 The unexpected content matches the expectation between offset 65536 and the end
    πŸŸ₯ The files have 65536 duplicate bytes in the middle
πŸ” Analyzing subject: 19
  πŸ“ Expected size: 162948 bytes
  ⚠️ Found 9.00% broken files (27/300)
  πŸ“ Checking 23 broken files (7.67%) of size 97412
    βœ… All files are the same
    ❌ The files exhibit an unexpected size
    πŸ“ The files differ in content at offset 65536 from the expectation
    🟰 The unexpected content matches the expectation between offset 65535 and the end
    πŸŸ₯ The files are missing 65536 bytes in the middle
  πŸ“ Checking 4 broken files (1.33%) of size 228484
    βœ… All files are the same
    ❌ The files exhibit an unexpected size
    πŸ“ The files differ in content at offset 65536 from the expectation
    🟰 The unexpected content matches the expectation between offset 65536 and the end
    πŸŸ₯ The files have 65536 duplicate bytes in the middle
πŸ” Analyzing subject: 20
  πŸ“ Expected size: 165035 bytes
  ⚠️ Found 5.00% broken files (15/300)
  πŸ“ Checking 15 broken files (5.00%) of size 99499
    βœ… All files are the same
    ❌ The files exhibit an unexpected size
    πŸ“ The files differ in content at offset 65536 from the expectation
    🟰 The unexpected content matches the expectation between offset 65535 and the end
    πŸŸ₯ The files are missing 65536 bytes in the middle
πŸ” Analyzing subject: 21
  πŸ“ Expected size: 201462 bytes
  ⚠️ Found 6.67% broken files (20/300)
  πŸ“ Checking 19 broken files (6.33%) of size 135926
    βœ… All files are the same
    ❌ The files exhibit an unexpected size
    πŸ“ The files differ in content at offset 65536 from the expectation
    🟰 The unexpected content matches the expectation between offset 65535 and the end
    πŸŸ₯ The files are missing 65536 bytes in the middle
  πŸ“ Checking 1 broken files (0.33%) of size 131072
    βœ… All files are the same
    βœ… The files are truncated at a multiple of 2^16
πŸ” Analyzing subject: 22
  πŸ“ Expected size: 186723 bytes
  ⚠️ Found 7.33% broken files (22/300)
  πŸ“ Checking 21 broken files (7.00%) of size 121187
    βœ… All files are the same
    ❌ The files exhibit an unexpected size
    πŸ“ The files differ in content at offset 65536 from the expectation
    🟰 The unexpected content matches the expectation between offset 65535 and the end
    πŸŸ₯ The files are missing 65536 bytes in the middle
  πŸ“ Checking 1 broken files (0.33%) of size 252259
    βœ… All files are the same
    ❌ The files exhibit an unexpected size
    πŸ“ The files differ in content at offset 65536 from the expectation
    🟰 The unexpected content matches the expectation between offset 65536 and the end
    πŸŸ₯ The files have 65536 duplicate bytes in the middle

As you can see, ALL images are broken at the magic number of 2^16 = 65536. An interesting observation is that some broken files are even larger than the original image in file size yet are visually still truncated. For all broken images considered here, either the second 65536 Bytes are missing, or the first 65536 Bytes are duplicated.

What does this all mean?

Given my findings, I presume this problem stems from a race condition or a data race. Somehow the FFE fails to properly handle files larger than 2^16 Bytes in some cases. The failure itself appears deterministic.

What can TeamSpeak do to fix this?

Use my findings to identify and fix the bug. :wink:

And now, for anyone who has read through this analysis, have a merry Christmas and a happy New Year!

giphy

10 Likes

This is very helpful (Dev was saying).

Let’s see what we can do out of this. Hope this something we can fix from our position.

7 Likes

Why don’t temporarily disable GIF support for profile images?

Based on my observations, it seems that the ts-client appends β€˜ts-frame=1’ to the element when the window is unfocused, causing confusion in Chrome due to the changing β€˜src.’ As a temporary solution, why not consider commenting out this function and use a normal static src?

This annoying bug is in there for years and the feature to use GIFs as a profile image is disabled anyway

1 Like

This is not really related to GIFs. It mainly happens to PNGs (I think).
You can see a bit more here On the First Frame Extractor. Let’s hope it will get fixed soon.

3 Likes